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