1 /* $OpenBSD: tty_endrun.c,v 1.8 2018/02/19 08:59:52 mpi Exp $ */
2
3 /*
4 * Copyright (c) 2008 Marc Balmer <mbalmer@openbsd.org>
5 * Copyright (c) 2009 Kevin Steves <stevesk@openbsd.org>
6 *
7 * Permission to use, copy, modify, and distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
10 *
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 */
19
20 /*
21 * A tty line discipline to decode the EndRun Technologies native
22 * time-of-day message.
23 * http://www.endruntechnologies.com/
24 */
25
26 /*
27 * EndRun Format:
28 *
29 * T YYYY DDD HH:MM:SS zZZ m<CR><LF>
30 *
31 * T is the Time Figure of Merit (TFOM) character (described below).
32 * This is the on-time character, transmitted during the first
33 * millisecond of each second.
34 *
35 * YYYY is the year
36 * DDD is the day-of-year
37 * : is the colon character (0x3A)
38 * HH is the hour of the day
39 * MM is the minute of the hour
40 * SS is the second of the minute
41 * z is the sign of the offset to UTC, + implies time is ahead of UTC.
42 * ZZ is the magnitude of the offset to UTC in units of half-hours.
43 * Non-zero only when the Timemode is Local.
44 * m is the Timemode character and is one of:
45 * G = GPS
46 * L = Local
47 * U = UTC
48 * <CR> is the ASCII carriage return character (0x0D)
49 * <LF> is the ASCII line feed character (0x0A)
50 */
51
52 #include <sys/param.h>
53 #include <sys/systm.h>
54 #include <sys/malloc.h>
55 #include <sys/sensors.h>
56 #include <sys/tty.h>
57 #include <sys/conf.h>
58 #include <sys/time.h>
59
60 #ifdef ENDRUN_DEBUG
61 #define DPRINTFN(n, x) do { if (endrundebug > (n)) printf x; } while (0)
62 int endrundebug = 0;
63 #else
64 #define DPRINTFN(n, x)
65 #endif
66 #define DPRINTF(x) DPRINTFN(0, x)
67
68 void endrunattach(int);
69
70 #define ENDRUNLEN 27 /* strlen("6 2009 018 20:41:17 +00 U\r\n") */
71 #define NUMFLDS 6
72 #ifdef ENDRUN_DEBUG
73 #define TRUSTTIME 30
74 #else
75 #define TRUSTTIME (10 * 60) /* 10 minutes */
76 #endif
77
78 int endrun_count, endrun_nxid;
79
80 struct endrun {
81 char cbuf[ENDRUNLEN]; /* receive buffer */
82 struct ksensor time; /* the timedelta sensor */
83 struct ksensor signal; /* signal status */
84 struct ksensordev timedev;
85 struct timespec ts; /* current timestamp */
86 struct timespec lts; /* timestamp of last TFOM */
87 struct timeout endrun_tout; /* invalidate sensor */
88 int64_t gap; /* gap between two sentences */
89 int64_t last; /* last time rcvd */
90 #define SYNC_SCAN 1 /* scanning for '\n' */
91 #define SYNC_EOL 2 /* '\n' seen, next char TFOM */
92 int sync;
93 int pos; /* position in rcv buffer */
94 int no_pps; /* no PPS although requested */
95 #ifdef ENDRUN_DEBUG
96 char tfom;
97 #endif
98 };
99
100 /* EndRun decoding */
101 void endrun_scan(struct endrun *, struct tty *);
102 void endrun_decode(struct endrun *, struct tty *, char *fld[], int fldcnt);
103
104 /* date and time conversion */
105 int endrun_atoi(char *s, int len);
106 int endrun_date_to_nano(char *s1, char *s2, int64_t *nano);
107 int endrun_time_to_nano(char *s, int64_t *nano);
108 int endrun_offset_to_nano(char *s, int64_t *nano);
109
110 /* degrade the timedelta sensor */
111 void endrun_timeout(void *);
112
113 void
endrunattach(int dummy)114 endrunattach(int dummy)
115 {
116 }
117
118 int
endrunopen(dev_t dev,struct tty * tp,struct proc * p)119 endrunopen(dev_t dev, struct tty *tp, struct proc *p)
120 {
121 struct endrun *np;
122 int error;
123
124 DPRINTF(("endrunopen\n"));
125 if (tp->t_line == ENDRUNDISC)
126 return ENODEV;
127 if ((error = suser(p)) != 0)
128 return error;
129 np = malloc(sizeof(struct endrun), M_DEVBUF, M_WAITOK|M_ZERO);
130 snprintf(np->timedev.xname, sizeof(np->timedev.xname), "endrun%d",
131 endrun_nxid++);
132 endrun_count++;
133 np->time.status = SENSOR_S_UNKNOWN;
134 np->time.type = SENSOR_TIMEDELTA;
135 #ifndef ENDRUN_DEBUG
136 np->time.flags = SENSOR_FINVALID;
137 #endif
138 sensor_attach(&np->timedev, &np->time);
139
140 np->signal.type = SENSOR_PERCENT;
141 np->signal.status = SENSOR_S_UNKNOWN;
142 np->signal.value = 100000LL;
143 strlcpy(np->signal.desc, "Signal", sizeof(np->signal.desc));
144 sensor_attach(&np->timedev, &np->signal);
145
146 np->sync = SYNC_SCAN;
147 #ifdef ENDRUN_DEBUG
148 np->tfom = '0';
149 #endif
150 tp->t_sc = (caddr_t)np;
151
152 error = linesw[TTYDISC].l_open(dev, tp, p);
153 if (error) {
154 free(np, M_DEVBUF, sizeof(*np));
155 tp->t_sc = NULL;
156 } else {
157 sensordev_install(&np->timedev);
158 timeout_set(&np->endrun_tout, endrun_timeout, np);
159 }
160
161 return error;
162 }
163
164 int
endrunclose(struct tty * tp,int flags,struct proc * p)165 endrunclose(struct tty *tp, int flags, struct proc *p)
166 {
167 struct endrun *np = (struct endrun *)tp->t_sc;
168
169 DPRINTF(("endrunclose\n"));
170 tp->t_line = TTYDISC; /* switch back to termios */
171 timeout_del(&np->endrun_tout);
172 sensordev_deinstall(&np->timedev);
173 free(np, M_DEVBUF, sizeof(*np));
174 tp->t_sc = NULL;
175 endrun_count--;
176 if (endrun_count == 0)
177 endrun_nxid = 0;
178 return linesw[TTYDISC].l_close(tp, flags, p);
179 }
180
181 /* collect EndRun sentence from tty */
182 int
endruninput(int c,struct tty * tp)183 endruninput(int c, struct tty *tp)
184 {
185 struct endrun *np = (struct endrun *)tp->t_sc;
186 struct timespec ts;
187 int64_t gap;
188 long tmin, tmax;
189
190 if (np->sync == SYNC_EOL) {
191 nanotime(&ts);
192 np->pos = 0;
193 np->sync = SYNC_SCAN;
194 np->cbuf[np->pos++] = c; /* TFOM char */
195
196 gap = (ts.tv_sec * 1000000000LL + ts.tv_nsec) -
197 (np->lts.tv_sec * 1000000000LL + np->lts.tv_nsec);
198
199 np->lts.tv_sec = ts.tv_sec;
200 np->lts.tv_nsec = ts.tv_nsec;
201
202 if (gap <= np->gap)
203 goto nogap;
204
205 np->ts.tv_sec = ts.tv_sec;
206 np->ts.tv_nsec = ts.tv_nsec;
207 np->gap = gap;
208
209 /*
210 * If a tty timestamp is available, make sure its value is
211 * reasonable by comparing against the timestamp just taken.
212 * If they differ by more than 2 seconds, assume no PPS signal
213 * is present, note the fact, and keep using the timestamp
214 * value. When this happens, the sensor state is set to
215 * CRITICAL later when the EndRun sentence is decoded.
216 */
217 if (tp->t_flags & (TS_TSTAMPDCDSET | TS_TSTAMPDCDCLR |
218 TS_TSTAMPCTSSET | TS_TSTAMPCTSCLR)) {
219 tmax = lmax(np->ts.tv_sec, tp->t_tv.tv_sec);
220 tmin = lmin(np->ts.tv_sec, tp->t_tv.tv_sec);
221 if (tmax - tmin > 1)
222 np->no_pps = 1;
223 else {
224 np->ts.tv_sec = tp->t_tv.tv_sec;
225 np->ts.tv_nsec = tp->t_tv.tv_usec *
226 1000L;
227 np->no_pps = 0;
228 }
229 }
230 } else if (c == '\n') {
231 if (np->pos == ENDRUNLEN - 1) {
232 /* don't copy '\n' into cbuf */
233 np->cbuf[np->pos] = '\0';
234 endrun_scan(np, tp);
235 }
236 np->sync = SYNC_EOL;
237 } else {
238 if (np->pos < ENDRUNLEN - 1)
239 np->cbuf[np->pos++] = c;
240 }
241
242 nogap:
243 /* pass data to termios */
244 return linesw[TTYDISC].l_rint(c, tp);
245 }
246
247 /* Scan the EndRun sentence just received */
248 void
endrun_scan(struct endrun * np,struct tty * tp)249 endrun_scan(struct endrun *np, struct tty *tp)
250 {
251 int fldcnt = 0, n;
252 char *fld[NUMFLDS], *cs;
253
254 DPRINTFN(1, ("%s\n", np->cbuf));
255 /* split into fields */
256 fld[fldcnt++] = &np->cbuf[0];
257 for (cs = NULL, n = 0; n < np->pos && cs == NULL; n++) {
258 switch (np->cbuf[n]) {
259 case '\r':
260 np->cbuf[n] = '\0';
261 cs = &np->cbuf[n + 1];
262 break;
263 case ' ':
264 if (fldcnt < NUMFLDS) {
265 np->cbuf[n] = '\0';
266 fld[fldcnt++] = &np->cbuf[n + 1];
267 } else {
268 DPRINTF(("endrun: nr of fields in sentence "
269 "exceeds expected: %d\n", NUMFLDS));
270 return;
271 }
272 break;
273 }
274 }
275 endrun_decode(np, tp, fld, fldcnt);
276 }
277
278 /* Decode the time string */
279 void
endrun_decode(struct endrun * np,struct tty * tp,char * fld[],int fldcnt)280 endrun_decode(struct endrun *np, struct tty *tp, char *fld[], int fldcnt)
281 {
282 int64_t date_nano, time_nano, offset_nano, endrun_now;
283 char tfom;
284 int jumped = 0;
285
286 if (fldcnt != NUMFLDS) {
287 DPRINTF(("endrun: field count mismatch, %d\n", fldcnt));
288 return;
289 }
290 if (endrun_time_to_nano(fld[3], &time_nano) == -1) {
291 DPRINTF(("endrun: illegal time, %s\n", fld[3]));
292 return;
293 }
294 if (endrun_date_to_nano(fld[1], fld[2], &date_nano) == -1) {
295 DPRINTF(("endrun: illegal date, %s %s\n", fld[1], fld[2]));
296 return;
297 }
298 offset_nano = 0;
299 /* only parse offset when timemode is local */
300 if (fld[5][0] == 'L' &&
301 endrun_offset_to_nano(fld[4], &offset_nano) == -1) {
302 DPRINTF(("endrun: illegal offset, %s\n", fld[4]));
303 return;
304 }
305
306 endrun_now = date_nano + time_nano + offset_nano;
307 if (endrun_now <= np->last) {
308 DPRINTF(("endrun: time not monotonically increasing "
309 "last %lld now %lld\n",
310 (long long)np->last, (long long)endrun_now));
311 jumped = 1;
312 }
313 np->last = endrun_now;
314 np->gap = 0LL;
315 #ifdef ENDRUN_DEBUG
316 if (np->time.status == SENSOR_S_UNKNOWN) {
317 np->time.status = SENSOR_S_OK;
318 timeout_add_sec(&np->endrun_tout, TRUSTTIME);
319 }
320 #endif
321
322 np->time.value = np->ts.tv_sec * 1000000000LL +
323 np->ts.tv_nsec - endrun_now;
324 np->time.tv.tv_sec = np->ts.tv_sec;
325 np->time.tv.tv_usec = np->ts.tv_nsec / 1000L;
326 if (np->time.status == SENSOR_S_UNKNOWN) {
327 np->time.status = SENSOR_S_OK;
328 np->time.flags &= ~SENSOR_FINVALID;
329 strlcpy(np->time.desc, "EndRun", sizeof(np->time.desc));
330 }
331 /*
332 * Only update the timeout if the clock reports the time as valid.
333 *
334 * Time Figure Of Merit (TFOM) values:
335 *
336 * 6 - time error is < 100 us
337 * 7 - time error is < 1 ms
338 * 8 - time error is < 10 ms
339 * 9 - time error is > 10 ms,
340 * unsynchronized state if never locked to CDMA
341 */
342
343 switch (tfom = fld[0][0]) {
344 case '6':
345 case '7':
346 case '8':
347 np->time.status = SENSOR_S_OK;
348 np->signal.status = SENSOR_S_OK;
349 break;
350 case '9':
351 np->signal.status = SENSOR_S_WARN;
352 break;
353 default:
354 DPRINTF(("endrun: invalid TFOM: '%c'\n", tfom));
355 np->signal.status = SENSOR_S_CRIT;
356 break;
357 }
358
359 #ifdef ENDRUN_DEBUG
360 if (np->tfom != tfom) {
361 DPRINTF(("endrun: TFOM changed from %c to %c\n",
362 np->tfom, tfom));
363 np->tfom = tfom;
364 }
365 #endif
366 if (jumped)
367 np->time.status = SENSOR_S_WARN;
368 if (np->time.status == SENSOR_S_OK)
369 timeout_add_sec(&np->endrun_tout, TRUSTTIME);
370
371 /*
372 * If tty timestamping is requested, but no PPS signal is present, set
373 * the sensor state to CRITICAL.
374 */
375 if (np->no_pps)
376 np->time.status = SENSOR_S_CRIT;
377 }
378
379 int
endrun_atoi(char * s,int len)380 endrun_atoi(char *s, int len)
381 {
382 int n;
383 char *p;
384
385 /* make sure the input contains only numbers */
386 for (n = 0, p = s; n < len && *p && *p >= '0' && *p <= '9'; n++, p++)
387 ;
388 if (n != len || *p != '\0')
389 return -1;
390
391 for (n = 0; *s; s++)
392 n = n * 10 + *s - '0';
393
394 return n;
395 }
396
397 /*
398 * Convert date fields from EndRun to nanoseconds since the epoch.
399 * The year string must be of the form YYYY .
400 * The day of year string must be of the form DDD .
401 * Return 0 on success, -1 if illegal characters are encountered.
402 */
403 int
endrun_date_to_nano(char * y,char * doy,int64_t * nano)404 endrun_date_to_nano(char *y, char *doy, int64_t *nano)
405 {
406 struct clock_ymdhms clock;
407 time_t secs;
408 int n, i;
409 int year_days = 365;
410 int month_days[] = {
411 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
412 };
413
414 #define FEBRUARY 2
415
416 #define LEAPYEAR(x) \
417 ((x) % 4 == 0 && \
418 (x) % 100 != 0) || \
419 (x) % 400 == 0
420
421 if ((n = endrun_atoi(y, 4)) == -1)
422 return -1;
423 clock.dt_year = n;
424
425 if (LEAPYEAR(n)) {
426 month_days[FEBRUARY]++;
427 year_days++;
428 }
429
430 if ((n = endrun_atoi(doy, 3)) == -1 || n == 0 || n > year_days)
431 return -1;
432
433 /* convert day of year to month, day */
434 for (i = 1; n > month_days[i]; i++) {
435 n -= month_days[i];
436 }
437 clock.dt_mon = i;
438 clock.dt_day = n;
439
440 DPRINTFN(1, ("mm/dd %d/%d\n", i, n));
441
442 clock.dt_hour = clock.dt_min = clock.dt_sec = 0;
443
444 secs = clock_ymdhms_to_secs(&clock);
445 *nano = secs * 1000000000LL;
446 return 0;
447 }
448
449 /*
450 * Convert time field from EndRun to nanoseconds since midnight.
451 * The string must be of the form HH:MM:SS .
452 * Return 0 on success, -1 if illegal characters are encountered.
453 */
454 int
endrun_time_to_nano(char * s,int64_t * nano)455 endrun_time_to_nano(char *s, int64_t *nano)
456 {
457 struct clock_ymdhms clock;
458 time_t secs;
459 int n;
460
461 if (s[2] != ':' || s[5] != ':')
462 return -1;
463
464 s[2] = '\0';
465 s[5] = '\0';
466
467 if ((n = endrun_atoi(&s[0], 2)) == -1 || n > 23)
468 return -1;
469 clock.dt_hour = n;
470 if ((n = endrun_atoi(&s[3], 2)) == -1 || n > 59)
471 return -1;
472 clock.dt_min = n;
473 if ((n = endrun_atoi(&s[6], 2)) == -1 || n > 60)
474 return -1;
475 clock.dt_sec = n;
476
477 DPRINTFN(1, ("hh:mm:ss %d:%d:%d\n", (int)clock.dt_hour,
478 (int)clock.dt_min,
479 (int)clock.dt_sec));
480 secs = clock.dt_hour * 3600
481 + clock.dt_min * 60
482 + clock.dt_sec;
483
484 DPRINTFN(1, ("secs %lu\n", (unsigned long)secs));
485
486 *nano = secs * 1000000000LL;
487 return 0;
488 }
489
490 int
endrun_offset_to_nano(char * s,int64_t * nano)491 endrun_offset_to_nano(char *s, int64_t *nano)
492 {
493 time_t secs;
494 int n;
495
496 if (!(s[0] == '+' || s[0] == '-'))
497 return -1;
498
499 if ((n = endrun_atoi(&s[1], 2)) == -1)
500 return -1;
501 secs = n * 30 * 60;
502
503 *nano = secs * 1000000000LL;
504 if (s[0] == '+')
505 *nano = -*nano;
506
507 DPRINTFN(1, ("offset secs %lu nanosecs %lld\n",
508 (unsigned long)secs, (long long)*nano));
509
510 return 0;
511 }
512
513 /*
514 * Degrade the sensor state if we received no EndRun string for more than
515 * TRUSTTIME seconds.
516 */
517 void
endrun_timeout(void * xnp)518 endrun_timeout(void *xnp)
519 {
520 struct endrun *np = xnp;
521
522 if (np->time.status == SENSOR_S_OK) {
523 np->time.status = SENSOR_S_WARN;
524 /*
525 * further degrade in TRUSTTIME seconds if no new valid EndRun
526 * strings are received.
527 */
528 timeout_add_sec(&np->endrun_tout, TRUSTTIME);
529 } else
530 np->time.status = SENSOR_S_CRIT;
531 }
532