1 /* $OpenBSD: tty_endrun.c,v 1.7 2015/12/21 21:49:02 sf 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 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 == ENDRUNDISC) 126 return ENODEV; 127 if ((error = suser(p, 0)) != 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 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 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 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 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 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 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 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); 529 } else 530 np->time.status = SENSOR_S_CRIT; 531 } 532