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