xref: /openbsd-src/sys/kern/tty_msts.c (revision 2b0358df1d88d06ef4139321dd05bd5e05d91eaf)
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