1 /* $OpenBSD: tty_msts.c,v 1.12 2009/01/12 16:45:38 stevesk Exp $ */ 2 3 /* 4 * Copyright (c) 2008 Marc Balmer <mbalmer@openbsd.org> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19 /* 20 * A tty line discipline to decode the Meinberg Standard Time String 21 * to get the time (http://www.meinberg.de/english/specs/timestr.htm). 22 */ 23 24 #include <sys/param.h> 25 #include <sys/systm.h> 26 #include <sys/proc.h> 27 #include <sys/malloc.h> 28 #include <sys/sensors.h> 29 #include <sys/tty.h> 30 #include <sys/conf.h> 31 #include <sys/time.h> 32 33 #ifdef MSTS_DEBUG 34 #define DPRINTFN(n, x) do { if (mstsdebug > (n)) printf x; } while (0) 35 int mstsdebug = 0; 36 #else 37 #define DPRINTFN(n, x) 38 #endif 39 #define DPRINTF(x) DPRINTFN(0, x) 40 41 int mstsopen(dev_t, struct tty *); 42 int mstsclose(struct tty *, int); 43 int mstsinput(int, struct tty *); 44 void mstsattach(int); 45 46 #define MSTSMAX 32 47 #define MAXFLDS 4 48 #ifdef MSTS_DEBUG 49 #define TRUSTTIME 30 50 #else 51 #define TRUSTTIME (10 * 60) /* 10 minutes */ 52 #endif 53 54 int msts_count, msts_nxid; 55 56 struct msts { 57 char cbuf[MSTSMAX]; /* receive buffer */ 58 struct ksensor time; /* the timedelta sensor */ 59 struct ksensor signal; /* signal status */ 60 struct ksensordev timedev; 61 struct timespec ts; /* current timestamp */ 62 struct timespec lts; /* timestamp of last <STX> */ 63 struct timeout msts_tout; /* invalidate sensor */ 64 int64_t gap; /* gap between two sentences */ 65 int64_t last; /* last time rcvd */ 66 int sync; /* if 1, waiting for <STX> */ 67 int pos; /* position in rcv buffer */ 68 int no_pps; /* no PPS although requested */ 69 }; 70 71 /* MSTS decoding */ 72 void msts_scan(struct msts *, struct tty *); 73 void msts_decode(struct msts *, struct tty *, char *fld[], int fldcnt); 74 75 /* date and time conversion */ 76 int msts_date_to_nano(char *s, int64_t *nano); 77 int msts_time_to_nano(char *s, int64_t *nano); 78 79 /* degrade the timedelta sensor */ 80 void msts_timeout(void *); 81 82 void 83 mstsattach(int dummy) 84 { 85 } 86 87 int 88 mstsopen(dev_t dev, struct tty *tp) 89 { 90 struct proc *p = curproc; 91 struct msts *np; 92 int error; 93 94 DPRINTF(("mstsopen\n")); 95 if (tp->t_line == MSTSDISC) 96 return ENODEV; 97 if ((error = suser(p, 0)) != 0) 98 return error; 99 np = malloc(sizeof(struct msts), M_DEVBUF, M_WAITOK|M_ZERO); 100 snprintf(np->timedev.xname, sizeof(np->timedev.xname), "msts%d", 101 msts_nxid++); 102 msts_count++; 103 np->time.status = SENSOR_S_UNKNOWN; 104 np->time.type = SENSOR_TIMEDELTA; 105 #ifndef MSTS_DEBUG 106 np->time.flags = SENSOR_FINVALID; 107 #endif 108 sensor_attach(&np->timedev, &np->time); 109 110 np->signal.type = SENSOR_PERCENT; 111 np->signal.status = SENSOR_S_UNKNOWN; 112 np->signal.value = 100000LL; 113 np->signal.flags = 0; 114 strlcpy(np->signal.desc, "Signal", sizeof(np->signal.desc)); 115 sensor_attach(&np->timedev, &np->signal); 116 117 np->sync = 1; 118 tp->t_sc = (caddr_t)np; 119 120 error = linesw[TTYDISC].l_open(dev, tp); 121 if (error) { 122 free(np, M_DEVBUF); 123 tp->t_sc = NULL; 124 } else { 125 sensordev_install(&np->timedev); 126 timeout_set(&np->msts_tout, msts_timeout, np); 127 } 128 129 return error; 130 } 131 132 int 133 mstsclose(struct tty *tp, int flags) 134 { 135 struct msts *np = (struct msts *)tp->t_sc; 136 137 tp->t_line = TTYDISC; /* switch back to termios */ 138 timeout_del(&np->msts_tout); 139 sensordev_deinstall(&np->timedev); 140 free(np, M_DEVBUF); 141 tp->t_sc = NULL; 142 msts_count--; 143 if (msts_count == 0) 144 msts_nxid = 0; 145 return linesw[TTYDISC].l_close(tp, flags); 146 } 147 148 /* collect MSTS sentence from tty */ 149 int 150 mstsinput(int c, struct tty *tp) 151 { 152 struct msts *np = (struct msts *)tp->t_sc; 153 struct timespec ts; 154 int64_t gap; 155 long tmin, tmax; 156 157 switch (c) { 158 case 2: /* ASCII <STX> */ 159 nanotime(&ts); 160 np->pos = np->sync = 0; 161 gap = (ts.tv_sec * 1000000000LL + ts.tv_nsec) - 162 (np->lts.tv_sec * 1000000000LL + np->lts.tv_nsec); 163 164 np->lts.tv_sec = ts.tv_sec; 165 np->lts.tv_nsec = ts.tv_nsec; 166 167 if (gap <= np->gap) 168 break; 169 170 np->ts.tv_sec = ts.tv_sec; 171 np->ts.tv_nsec = ts.tv_nsec; 172 np->gap = gap; 173 174 /* 175 * If a tty timestamp is available, make sure its value is 176 * reasonable by comparing against the timestamp just taken. 177 * If they differ by more than 2 seconds, assume no PPS signal 178 * is present, note the fact, and keep using the timestamp 179 * value. When this happens, the sensor state is set to 180 * CRITICAL later when the MSTS sentence is decoded. 181 */ 182 if (tp->t_flags & (TS_TSTAMPDCDSET | TS_TSTAMPDCDCLR | 183 TS_TSTAMPCTSSET | TS_TSTAMPCTSCLR)) { 184 tmax = lmax(np->ts.tv_sec, tp->t_tv.tv_sec); 185 tmin = lmin(np->ts.tv_sec, tp->t_tv.tv_sec); 186 if (tmax - tmin > 1) 187 np->no_pps = 1; 188 else { 189 np->ts.tv_sec = tp->t_tv.tv_sec; 190 np->ts.tv_nsec = tp->t_tv.tv_usec * 191 1000L; 192 np->no_pps = 0; 193 } 194 } 195 break; 196 case 3: /* ASCII <ETX> */ 197 if (!np->sync) { 198 np->cbuf[np->pos] = '\0'; 199 msts_scan(np, tp); 200 np->sync = 1; 201 } 202 break; 203 default: 204 if (!np->sync && np->pos < (MSTSMAX - 1)) 205 np->cbuf[np->pos++] = c; 206 break; 207 } 208 /* pass data to termios */ 209 return linesw[TTYDISC].l_rint(c, tp); 210 } 211 212 /* Scan the MSTS sentence just received */ 213 void 214 msts_scan(struct msts *np, struct tty *tp) 215 { 216 int fldcnt = 0, n; 217 char *fld[MAXFLDS], *cs; 218 219 /* split into fields */ 220 fld[fldcnt++] = &np->cbuf[0]; 221 for (cs = NULL, n = 0; n < np->pos && cs == NULL; n++) { 222 switch (np->cbuf[n]) { 223 case 3: /* ASCII <ETX> */ 224 np->cbuf[n] = '\0'; 225 cs = &np->cbuf[n + 1]; 226 break; 227 case ';': 228 if (fldcnt < MAXFLDS) { 229 np->cbuf[n] = '\0'; 230 fld[fldcnt++] = &np->cbuf[n + 1]; 231 } else { 232 DPRINTF(("nr of fields in sentence exceeds " 233 "maximum of %d\n", MAXFLDS)); 234 return; 235 } 236 break; 237 } 238 } 239 msts_decode(np, tp, fld, fldcnt); 240 } 241 242 /* Decode the time string */ 243 void 244 msts_decode(struct msts *np, struct tty *tp, char *fld[], int fldcnt) 245 { 246 int64_t date_nano, time_nano, msts_now; 247 248 if (fldcnt != MAXFLDS) { 249 DPRINTF(("msts: field count mismatch, %d\n", fldcnt)); 250 return; 251 } 252 if (msts_time_to_nano(fld[2], &time_nano)) { 253 DPRINTF(("msts: illegal time, %s\n", fld[2])); 254 return; 255 } 256 if (msts_date_to_nano(fld[0], &date_nano)) { 257 DPRINTF(("msts: illegal date, %s\n", fld[0])); 258 return; 259 } 260 msts_now = date_nano + time_nano; 261 if ( fld[3][2] == ' ' ) /* received time in CET */ 262 msts_now = msts_now - 3600 * 1000000000LL; 263 if ( fld[3][2] == 'S' ) /* received time in CEST */ 264 msts_now = msts_now - 2 * 3600 * 1000000000LL; 265 if (msts_now <= np->last) { 266 DPRINTF(("msts: time not monotonically increasing\n")); 267 return; 268 } 269 np->last = msts_now; 270 np->gap = 0LL; 271 #ifdef MSTS_DEBUG 272 if (np->time.status == SENSOR_S_UNKNOWN) { 273 np->time.status = SENSOR_S_OK; 274 timeout_add_sec(&np->msts_tout, TRUSTTIME); 275 } 276 #endif 277 278 np->time.value = np->ts.tv_sec * 1000000000LL + 279 np->ts.tv_nsec - msts_now; 280 np->time.tv.tv_sec = np->ts.tv_sec; 281 np->time.tv.tv_usec = np->ts.tv_nsec / 1000L; 282 if (np->time.status == SENSOR_S_UNKNOWN) { 283 np->time.status = SENSOR_S_OK; 284 np->time.flags &= ~SENSOR_FINVALID; 285 if (fldcnt != 13) 286 strlcpy(np->time.desc, "MSTS", sizeof(np->time.desc)); 287 } 288 /* 289 * only update the timeout if the clock reports the time a valid, 290 * the status is reported in fld[3][0] and fld[3][1] as follows: 291 * fld[3][0] == '#' critical 292 * fld[3][0] == ' ' && fld[3][1] == '*' warning 293 * fld[3][0] == ' ' && fld[3][1] == ' ' ok 294 */ 295 if (fld[3][0] == ' ' && fld[3][1] == ' ') { 296 np->time.status = SENSOR_S_OK; 297 np->signal.status = SENSOR_S_OK; 298 timeout_add_sec(&np->msts_tout, TRUSTTIME); 299 } else 300 np->signal.status = SENSOR_S_WARN; 301 302 /* 303 * If tty timestamping is requested, but no PPS signal is present, set 304 * the sensor state to CRITICAL. 305 */ 306 if (np->no_pps) 307 np->time.status = SENSOR_S_CRIT; 308 } 309 310 /* 311 * Convert date field from MSTS to nanoseconds since the epoch. 312 * The string must be of the form D:DD.MM.YY . 313 * Return 0 on success, -1 if illegal characters are encountered. 314 */ 315 int 316 msts_date_to_nano(char *s, int64_t *nano) 317 { 318 struct clock_ymdhms ymd; 319 time_t secs; 320 char *p; 321 int n; 322 323 if (s[0] != 'D' || s[1] != ':' || s[4] != '.' || s[7] != '.') 324 return -1; 325 326 /* shift numbers to DDMMYY */ 327 s[0]=s[2]; 328 s[1]=s[3]; 329 s[2]=s[5]; 330 s[3]=s[6]; 331 s[4]=s[8]; 332 s[5]=s[9]; 333 s[6]='\0'; 334 335 /* make sure the input contains only numbers and is six digits long */ 336 for (n = 0, p = s; n < 6 && *p && *p >= '0' && *p <= '9'; n++, p++) 337 ; 338 if (n != 6 || (*p != '\0')) 339 return -1; 340 341 ymd.dt_year = 2000 + (s[4] - '0') * 10 + (s[5] - '0'); 342 ymd.dt_mon = (s[2] - '0') * 10 + (s[3] - '0'); 343 ymd.dt_day = (s[0] - '0') * 10 + (s[1] - '0'); 344 ymd.dt_hour = ymd.dt_min = ymd.dt_sec = 0; 345 346 secs = clock_ymdhms_to_secs(&ymd); 347 *nano = secs * 1000000000LL; 348 return 0; 349 } 350 351 /* 352 * Convert time field from MSTS to nanoseconds since midnight. 353 * The string must be of the form U:HH.MM.SS . 354 * Return 0 on success, -1 if illegal characters are encountered. 355 */ 356 int 357 msts_time_to_nano(char *s, int64_t *nano) 358 { 359 long fac = 36000L, div = 6L, secs = 0L; 360 char ul = '2'; 361 int n; 362 363 if (s[0] != 'U' || s[1] != ':' || s[4] != '.' || s[7] != '.') 364 return -1; 365 366 /* shift numbers to HHMMSS */ 367 s[0]=s[2]; 368 s[1]=s[3]; 369 s[2]=s[5]; 370 s[3]=s[6]; 371 s[4]=s[8]; 372 s[5]=s[9]; 373 s[6]='\0'; 374 375 for (n = 0, secs = 0; fac && *s && *s >= '0' && *s <= ul; s++, n++) { 376 secs += (*s - '0') * fac; 377 div = 16 - div; 378 fac /= div; 379 switch (n) { 380 case 0: 381 if (*s <= '1') 382 ul = '9'; 383 else 384 ul = '3'; 385 break; 386 case 1: 387 case 3: 388 ul = '5'; 389 break; 390 case 2: 391 case 4: 392 ul = '9'; 393 break; 394 } 395 } 396 if (fac) 397 return -1; 398 399 if (*s != '\0') 400 return -1; 401 402 *nano = secs * 1000000000LL; 403 return 0; 404 } 405 406 /* 407 * Degrade the sensor state if we received no MSTS string for more than 408 * TRUSTTIME seconds. 409 */ 410 void 411 msts_timeout(void *xnp) 412 { 413 struct msts *np = xnp; 414 415 if (np->time.status == SENSOR_S_OK) { 416 np->time.status = SENSOR_S_WARN; 417 /* 418 * further degrade in TRUSTTIME seconds if no new valid MSTS 419 * strings are received. 420 */ 421 timeout_add_sec(&np->msts_tout, TRUSTTIME); 422 } else 423 np->time.status = SENSOR_S_CRIT; 424 } 425