Line data Source code
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
114 0 : endrunattach(int dummy)
115 : {
116 0 : }
117 :
118 : int
119 0 : endrunopen(dev_t dev, struct tty *tp, struct proc *p)
120 : {
121 : struct endrun *np;
122 : int error;
123 :
124 : DPRINTF(("endrunopen\n"));
125 0 : if (tp->t_line == ENDRUNDISC)
126 0 : return ENODEV;
127 0 : if ((error = suser(p)) != 0)
128 0 : return error;
129 0 : np = malloc(sizeof(struct endrun), M_DEVBUF, M_WAITOK|M_ZERO);
130 0 : snprintf(np->timedev.xname, sizeof(np->timedev.xname), "endrun%d",
131 0 : endrun_nxid++);
132 0 : endrun_count++;
133 0 : np->time.status = SENSOR_S_UNKNOWN;
134 0 : np->time.type = SENSOR_TIMEDELTA;
135 : #ifndef ENDRUN_DEBUG
136 0 : np->time.flags = SENSOR_FINVALID;
137 : #endif
138 0 : sensor_attach(&np->timedev, &np->time);
139 :
140 0 : np->signal.type = SENSOR_PERCENT;
141 0 : np->signal.status = SENSOR_S_UNKNOWN;
142 0 : np->signal.value = 100000LL;
143 0 : strlcpy(np->signal.desc, "Signal", sizeof(np->signal.desc));
144 0 : sensor_attach(&np->timedev, &np->signal);
145 :
146 0 : np->sync = SYNC_SCAN;
147 : #ifdef ENDRUN_DEBUG
148 : np->tfom = '0';
149 : #endif
150 0 : tp->t_sc = (caddr_t)np;
151 :
152 0 : error = linesw[TTYDISC].l_open(dev, tp, p);
153 0 : if (error) {
154 0 : free(np, M_DEVBUF, sizeof(*np));
155 0 : tp->t_sc = NULL;
156 0 : } else {
157 0 : sensordev_install(&np->timedev);
158 0 : timeout_set(&np->endrun_tout, endrun_timeout, np);
159 : }
160 :
161 0 : return error;
162 0 : }
163 :
164 : int
165 0 : endrunclose(struct tty *tp, int flags, struct proc *p)
166 : {
167 0 : struct endrun *np = (struct endrun *)tp->t_sc;
168 :
169 : DPRINTF(("endrunclose\n"));
170 0 : tp->t_line = TTYDISC; /* switch back to termios */
171 0 : timeout_del(&np->endrun_tout);
172 0 : sensordev_deinstall(&np->timedev);
173 0 : free(np, M_DEVBUF, sizeof(*np));
174 0 : tp->t_sc = NULL;
175 0 : endrun_count--;
176 0 : if (endrun_count == 0)
177 0 : endrun_nxid = 0;
178 0 : return linesw[TTYDISC].l_close(tp, flags, p);
179 : }
180 :
181 : /* collect EndRun sentence from tty */
182 : int
183 0 : endruninput(int c, struct tty *tp)
184 : {
185 0 : struct endrun *np = (struct endrun *)tp->t_sc;
186 0 : struct timespec ts;
187 : int64_t gap;
188 : long tmin, tmax;
189 :
190 0 : if (np->sync == SYNC_EOL) {
191 0 : nanotime(&ts);
192 0 : np->pos = 0;
193 0 : np->sync = SYNC_SCAN;
194 0 : np->cbuf[np->pos++] = c; /* TFOM char */
195 :
196 0 : gap = (ts.tv_sec * 1000000000LL + ts.tv_nsec) -
197 0 : (np->lts.tv_sec * 1000000000LL + np->lts.tv_nsec);
198 :
199 0 : np->lts.tv_sec = ts.tv_sec;
200 0 : np->lts.tv_nsec = ts.tv_nsec;
201 :
202 0 : if (gap <= np->gap)
203 : goto nogap;
204 :
205 0 : np->ts.tv_sec = ts.tv_sec;
206 0 : np->ts.tv_nsec = ts.tv_nsec;
207 0 : 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 0 : if (tp->t_flags & (TS_TSTAMPDCDSET | TS_TSTAMPDCDCLR |
218 : TS_TSTAMPCTSSET | TS_TSTAMPCTSCLR)) {
219 0 : tmax = lmax(np->ts.tv_sec, tp->t_tv.tv_sec);
220 0 : tmin = lmin(np->ts.tv_sec, tp->t_tv.tv_sec);
221 0 : if (tmax - tmin > 1)
222 0 : np->no_pps = 1;
223 : else {
224 0 : np->ts.tv_sec = tp->t_tv.tv_sec;
225 0 : np->ts.tv_nsec = tp->t_tv.tv_usec *
226 : 1000L;
227 0 : np->no_pps = 0;
228 : }
229 : }
230 0 : } else if (c == '\n') {
231 0 : if (np->pos == ENDRUNLEN - 1) {
232 : /* don't copy '\n' into cbuf */
233 0 : np->cbuf[np->pos] = '\0';
234 0 : endrun_scan(np, tp);
235 0 : }
236 0 : np->sync = SYNC_EOL;
237 0 : } else {
238 0 : if (np->pos < ENDRUNLEN - 1)
239 0 : np->cbuf[np->pos++] = c;
240 : }
241 :
242 : nogap:
243 : /* pass data to termios */
244 0 : return linesw[TTYDISC].l_rint(c, tp);
245 0 : }
246 :
247 : /* Scan the EndRun sentence just received */
248 : void
249 0 : endrun_scan(struct endrun *np, struct tty *tp)
250 : {
251 : int fldcnt = 0, n;
252 0 : char *fld[NUMFLDS], *cs;
253 :
254 : DPRINTFN(1, ("%s\n", np->cbuf));
255 : /* split into fields */
256 0 : fld[fldcnt++] = &np->cbuf[0];
257 0 : for (cs = NULL, n = 0; n < np->pos && cs == NULL; n++) {
258 0 : switch (np->cbuf[n]) {
259 : case '\r':
260 0 : np->cbuf[n] = '\0';
261 0 : cs = &np->cbuf[n + 1];
262 0 : break;
263 : case ' ':
264 0 : if (fldcnt < NUMFLDS) {
265 0 : np->cbuf[n] = '\0';
266 0 : fld[fldcnt++] = &np->cbuf[n + 1];
267 : } else {
268 : DPRINTF(("endrun: nr of fields in sentence "
269 : "exceeds expected: %d\n", NUMFLDS));
270 0 : return;
271 : }
272 0 : break;
273 : }
274 : }
275 0 : endrun_decode(np, tp, fld, fldcnt);
276 0 : }
277 :
278 : /* Decode the time string */
279 : void
280 0 : endrun_decode(struct endrun *np, struct tty *tp, char *fld[], int fldcnt)
281 : {
282 0 : int64_t date_nano, time_nano, offset_nano, endrun_now;
283 : char tfom;
284 : int jumped = 0;
285 :
286 0 : if (fldcnt != NUMFLDS) {
287 : DPRINTF(("endrun: field count mismatch, %d\n", fldcnt));
288 0 : return;
289 : }
290 0 : if (endrun_time_to_nano(fld[3], &time_nano) == -1) {
291 : DPRINTF(("endrun: illegal time, %s\n", fld[3]));
292 0 : return;
293 : }
294 0 : 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 0 : return;
297 : }
298 0 : offset_nano = 0;
299 : /* only parse offset when timemode is local */
300 0 : if (fld[5][0] == 'L' &&
301 0 : endrun_offset_to_nano(fld[4], &offset_nano) == -1) {
302 : DPRINTF(("endrun: illegal offset, %s\n", fld[4]));
303 0 : return;
304 : }
305 :
306 0 : endrun_now = date_nano + time_nano + offset_nano;
307 0 : 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 0 : }
313 0 : np->last = endrun_now;
314 0 : 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 0 : np->time.value = np->ts.tv_sec * 1000000000LL +
323 0 : np->ts.tv_nsec - endrun_now;
324 0 : np->time.tv.tv_sec = np->ts.tv_sec;
325 0 : np->time.tv.tv_usec = np->ts.tv_nsec / 1000L;
326 0 : if (np->time.status == SENSOR_S_UNKNOWN) {
327 0 : np->time.status = SENSOR_S_OK;
328 0 : np->time.flags &= ~SENSOR_FINVALID;
329 0 : strlcpy(np->time.desc, "EndRun", sizeof(np->time.desc));
330 0 : }
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 0 : switch (tfom = fld[0][0]) {
344 : case '6':
345 : case '7':
346 : case '8':
347 0 : np->time.status = SENSOR_S_OK;
348 0 : np->signal.status = SENSOR_S_OK;
349 0 : break;
350 : case '9':
351 0 : np->signal.status = SENSOR_S_WARN;
352 0 : break;
353 : default:
354 : DPRINTF(("endrun: invalid TFOM: '%c'\n", tfom));
355 0 : np->signal.status = SENSOR_S_CRIT;
356 0 : 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 0 : if (jumped)
367 0 : np->time.status = SENSOR_S_WARN;
368 0 : if (np->time.status == SENSOR_S_OK)
369 0 : 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 0 : if (np->no_pps)
376 0 : np->time.status = SENSOR_S_CRIT;
377 0 : }
378 :
379 : int
380 0 : endrun_atoi(char *s, int len)
381 : {
382 : int n;
383 : char *p;
384 :
385 : /* make sure the input contains only numbers */
386 0 : for (n = 0, p = s; n < len && *p && *p >= '0' && *p <= '9'; n++, p++)
387 : ;
388 0 : if (n != len || *p != '\0')
389 0 : return -1;
390 :
391 0 : for (n = 0; *s; s++)
392 0 : n = n * 10 + *s - '0';
393 :
394 0 : return n;
395 0 : }
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
404 0 : endrun_date_to_nano(char *y, char *doy, int64_t *nano)
405 : {
406 0 : struct clock_ymdhms clock;
407 : time_t secs;
408 : int n, i;
409 : int year_days = 365;
410 0 : 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 0 : if ((n = endrun_atoi(y, 4)) == -1)
422 0 : return -1;
423 0 : clock.dt_year = n;
424 :
425 0 : if (LEAPYEAR(n)) {
426 0 : month_days[FEBRUARY]++;
427 : year_days++;
428 0 : }
429 :
430 0 : if ((n = endrun_atoi(doy, 3)) == -1 || n == 0 || n > year_days)
431 0 : return -1;
432 :
433 : /* convert day of year to month, day */
434 0 : for (i = 1; n > month_days[i]; i++) {
435 0 : n -= month_days[i];
436 : }
437 0 : clock.dt_mon = i;
438 0 : clock.dt_day = n;
439 :
440 : DPRINTFN(1, ("mm/dd %d/%d\n", i, n));
441 :
442 0 : clock.dt_hour = clock.dt_min = clock.dt_sec = 0;
443 :
444 0 : secs = clock_ymdhms_to_secs(&clock);
445 0 : *nano = secs * 1000000000LL;
446 0 : return 0;
447 0 : }
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
455 0 : endrun_time_to_nano(char *s, int64_t *nano)
456 : {
457 : struct clock_ymdhms clock;
458 : time_t secs;
459 : int n;
460 :
461 0 : if (s[2] != ':' || s[5] != ':')
462 0 : return -1;
463 :
464 0 : s[2] = '\0';
465 0 : s[5] = '\0';
466 :
467 0 : if ((n = endrun_atoi(&s[0], 2)) == -1 || n > 23)
468 0 : return -1;
469 0 : clock.dt_hour = n;
470 0 : if ((n = endrun_atoi(&s[3], 2)) == -1 || n > 59)
471 0 : return -1;
472 0 : clock.dt_min = n;
473 0 : if ((n = endrun_atoi(&s[6], 2)) == -1 || n > 60)
474 0 : return -1;
475 0 : 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 0 : secs = clock.dt_hour * 3600
481 0 : + clock.dt_min * 60
482 0 : + clock.dt_sec;
483 :
484 : DPRINTFN(1, ("secs %lu\n", (unsigned long)secs));
485 :
486 0 : *nano = secs * 1000000000LL;
487 0 : return 0;
488 0 : }
489 :
490 : int
491 0 : endrun_offset_to_nano(char *s, int64_t *nano)
492 : {
493 : time_t secs;
494 : int n;
495 :
496 0 : if (!(s[0] == '+' || s[0] == '-'))
497 0 : return -1;
498 :
499 0 : if ((n = endrun_atoi(&s[1], 2)) == -1)
500 0 : return -1;
501 0 : secs = n * 30 * 60;
502 :
503 0 : *nano = secs * 1000000000LL;
504 0 : if (s[0] == '+')
505 0 : *nano = -*nano;
506 :
507 : DPRINTFN(1, ("offset secs %lu nanosecs %lld\n",
508 : (unsigned long)secs, (long long)*nano));
509 :
510 0 : return 0;
511 0 : }
512 :
513 : /*
514 : * Degrade the sensor state if we received no EndRun string for more than
515 : * TRUSTTIME seconds.
516 : */
517 : void
518 0 : endrun_timeout(void *xnp)
519 : {
520 0 : struct endrun *np = xnp;
521 :
522 0 : if (np->time.status == SENSOR_S_OK) {
523 0 : np->time.status = SENSOR_S_WARN;
524 : /*
525 : * further degrade in TRUSTTIME seconds if no new valid EndRun
526 : * strings are received.
527 : */
528 0 : timeout_add_sec(&np->endrun_tout, TRUSTTIME);
529 0 : } else
530 0 : np->time.status = SENSOR_S_CRIT;
531 0 : }
|