diff options
| author | 2009-05-06 18:21:23 +0000 | |
|---|---|---|
| committer | 2009-05-06 18:21:23 +0000 | |
| commit | 61406af6610968943778d6bc8b98b977de2caebb (patch) | |
| tree | 5e12f90aca22be5500841b4724cf865e7661d473 /sys/kern/tty_endrun.c | |
| parent | remove erroneous fldcnt test. fldcnt can never be 13 here. this is (diff) | |
| download | wireguard-openbsd-61406af6610968943778d6bc8b98b977de2caebb.tar.xz wireguard-openbsd-61406af6610968943778d6bc8b98b977de2caebb.zip | |
endrun(4) - EndRun Technologies native time-of-day message timedelta
sensor. Based on msts(4). Tested with Praecis Ct
(http://www.endruntechnologies.com/network-time-source.htm).
help and feedback mbalmer
'no problem with this sensor going in' deraadt
Diffstat (limited to 'sys/kern/tty_endrun.c')
| -rw-r--r-- | sys/kern/tty_endrun.c | 532 |
1 files changed, 532 insertions, 0 deletions
diff --git a/sys/kern/tty_endrun.c b/sys/kern/tty_endrun.c new file mode 100644 index 00000000000..202b56b4cf2 --- /dev/null +++ b/sys/kern/tty_endrun.c @@ -0,0 +1,532 @@ +/* $OpenBSD: tty_endrun.c,v 1.1 2009/05/06 18:21:23 stevesk 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/proc.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) + +int endrunopen(dev_t, struct tty *); +int endrunclose(struct tty *, int); +int endruninput(int, struct tty *); +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 = curproc; + struct endrun *np; + int error; + + DPRINTF(("endrunopen\n")); + if (tp->t_line == ENDRUNDISC) + return ENODEV; + if ((error = suser(p, 0)) != 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); + if (error) { + free(np, M_DEVBUF); + 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 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); + tp->t_sc = NULL; + endrun_count--; + if (endrun_count == 0) + endrun_nxid = 0; + return linesw[TTYDISC].l_close(tp, flags); +} + +/* 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; + + 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)); + return; + } + 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; + timeout_add_sec(&np->endrun_tout, TRUSTTIME); + 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 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; +} |
