123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535 |
- /* $OpenBSD: tty_endrun.c,v 1.6 2014/11/03 03:08:00 deraadt 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)
- int endrunopen(dev_t, struct tty *, struct proc *p);
- int endrunclose(struct tty *, int, struct proc *p);
- 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)
- {
- 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, 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;
- }
|