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