File: | kern/tty_endrun.c |
Warning: | line 343, column 10 Although the value stored to 'tfom' is used in the enclosing expression, the value is never actually read from 'tfom' |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
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 ENDRUNLEN27 27 /* strlen("6 2009 018 20:41:17 +00 U\r\n") */ |
71 | #define NUMFLDS6 6 |
72 | #ifdef ENDRUN_DEBUG |
73 | #define TRUSTTIME(10 * 60) 30 |
74 | #else |
75 | #define TRUSTTIME(10 * 60) (10 * 60) /* 10 minutes */ |
76 | #endif |
77 | |
78 | int endrun_count, endrun_nxid; |
79 | |
80 | struct endrun { |
81 | char cbuf[ENDRUNLEN27]; /* 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_SCAN1 1 /* scanning for '\n' */ |
91 | #define SYNC_EOL2 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 | endrunattach(int dummy) |
115 | { |
116 | } |
117 | |
118 | int |
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 == ENDRUNDISC9) |
126 | return ENODEV19; |
127 | if ((error = suser(p)) != 0) |
128 | return error; |
129 | np = malloc(sizeof(struct endrun), M_DEVBUF2, M_WAITOK0x0001|M_ZERO0x0008); |
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_FINVALID0x0001; |
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_SCAN1; |
147 | #ifdef ENDRUN_DEBUG |
148 | np->tfom = '0'; |
149 | #endif |
150 | tp->t_sc = (caddr_t)np; |
151 | |
152 | error = linesw[TTYDISC0].l_open(dev, tp, p); |
153 | if (error) { |
154 | free(np, M_DEVBUF2, sizeof(*np)); |
155 | tp->t_sc = NULL((void *)0); |
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 |
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 = TTYDISC0; /* switch back to termios */ |
171 | timeout_del(&np->endrun_tout); |
172 | sensordev_deinstall(&np->timedev); |
173 | free(np, M_DEVBUF2, sizeof(*np)); |
174 | tp->t_sc = NULL((void *)0); |
175 | endrun_count--; |
176 | if (endrun_count == 0) |
177 | endrun_nxid = 0; |
178 | return linesw[TTYDISC0].l_close(tp, flags, p); |
179 | } |
180 | |
181 | /* collect EndRun sentence from tty */ |
182 | int |
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_EOL2) { |
191 | nanotime(&ts); |
192 | np->pos = 0; |
193 | np->sync = SYNC_SCAN1; |
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_TSTAMPDCDSET0x10000 | TS_TSTAMPDCDCLR0x20000 | |
218 | TS_TSTAMPCTSSET0x40000 | TS_TSTAMPCTSCLR0x80000)) { |
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 == ENDRUNLEN27 - 1) { |
232 | /* don't copy '\n' into cbuf */ |
233 | np->cbuf[np->pos] = '\0'; |
234 | endrun_scan(np, tp); |
235 | } |
236 | np->sync = SYNC_EOL2; |
237 | } else { |
238 | if (np->pos < ENDRUNLEN27 - 1) |
239 | np->cbuf[np->pos++] = c; |
240 | } |
241 | |
242 | nogap: |
243 | /* pass data to termios */ |
244 | return linesw[TTYDISC0].l_rint(c, tp); |
245 | } |
246 | |
247 | /* Scan the EndRun sentence just received */ |
248 | void |
249 | endrun_scan(struct endrun *np, struct tty *tp) |
250 | { |
251 | int fldcnt = 0, n; |
252 | char *fld[NUMFLDS6], *cs; |
253 | |
254 | DPRINTFN(1, ("%s\n", np->cbuf)); |
255 | /* split into fields */ |
256 | fld[fldcnt++] = &np->cbuf[0]; |
257 | for (cs = NULL((void *)0), n = 0; n < np->pos && cs == NULL((void *)0); 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 < NUMFLDS6) { |
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 |
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 != NUMFLDS6) { |
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(10 * 60)); |
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_FINVALID0x0001; |
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]) { |
Although the value stored to 'tfom' is used in the enclosing expression, the value is never actually read from 'tfom' | |
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(10 * 60)); |
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 |
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 |
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 FEBRUARY2 2 |
415 | |
416 | #define LEAPYEAR(x)((x) % 4 == 0 && (x) % 100 != 0) || (x) % 400 == 0 \ |
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)((n) % 4 == 0 && (n) % 100 != 0) || (n) % 400 == 0) { |
426 | month_days[FEBRUARY2]++; |
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 |
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 |
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 |
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(10 * 60)); |
529 | } else |
530 | np->time.status = SENSOR_S_CRIT; |
531 | } |