/* $OpenBSD: tty_msts.c,v 1.21 2018/02/19 08:59:52 mpi Exp $ */ /* * Copyright (c) 2008 Marc Balmer * * 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 #include #include #include #include #include #include #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 */ 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 */ 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 */ 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 */ 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 */ 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; }