xref: /openbsd/sys/kern/tty_endrun.c (revision 74698e67)
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