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