xref: /openbsd-src/sys/kern/tty_nmea.c (revision a28daedfc357b214be5c701aa8ba8adb29a7f1c2)
1 /*	$OpenBSD: tty_nmea.c,v 1.34 2009/04/26 02:25:36 cnst Exp $ */
2 
3 /*
4  * Copyright (c) 2006, 2007, 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 /* A tty line discipline to decode NMEA 0183 data to get the time. */
20 
21 #include <sys/param.h>
22 #include <sys/systm.h>
23 #include <sys/proc.h>
24 #include <sys/malloc.h>
25 #include <sys/sensors.h>
26 #include <sys/tty.h>
27 #include <sys/conf.h>
28 #include <sys/time.h>
29 
30 #ifdef NMEA_DEBUG
31 #define DPRINTFN(n, x)	do { if (nmeadebug > (n)) printf x; } while (0)
32 int nmeadebug = 0;
33 #else
34 #define DPRINTFN(n, x)
35 #endif
36 #define DPRINTF(x)	DPRINTFN(0, x)
37 
38 int	nmeaopen(dev_t, struct tty *);
39 int	nmeaclose(struct tty *, int);
40 int	nmeainput(int, struct tty *);
41 void	nmeaattach(int);
42 
43 #define NMEAMAX		82
44 #define MAXFLDS		32
45 #ifdef NMEA_DEBUG
46 #define TRUSTTIME	30
47 #else
48 #define TRUSTTIME	(10 * 60)	/* 10 minutes */
49 #endif
50 
51 int nmea_count, nmea_nxid;
52 
53 struct nmea {
54 	char			cbuf[NMEAMAX];	/* receive buffer */
55 	struct ksensor		time;		/* the timedelta sensor */
56 	struct ksensor		signal;		/* signal status */
57 	struct ksensordev	timedev;
58 	struct timespec		ts;		/* current timestamp */
59 	struct timespec		lts;		/* timestamp of last '$' seen */
60 	struct timeout		nmea_tout;	/* invalidate sensor */
61 	int64_t			gap;		/* gap between two sentences */
62 #ifdef NMEA_DEBUG
63 	int			gapno;
64 #endif
65 	int64_t			last;		/* last time rcvd */
66 	int			sync;		/* if 1, waiting for '$' */
67 	int			pos;		/* position in rcv buffer */
68 	int			no_pps;		/* no PPS although requested */
69 	char			mode;		/* GPS mode */
70 };
71 
72 /* NMEA decoding */
73 void	nmea_scan(struct nmea *, struct tty *);
74 void	nmea_gprmc(struct nmea *, struct tty *, char *fld[], int fldcnt);
75 
76 /* date and time conversion */
77 int	nmea_date_to_nano(char *s, int64_t *nano);
78 int	nmea_time_to_nano(char *s, int64_t *nano);
79 
80 #if NMEA_POS_IN_DESC
81 /* longitude and latitude formatting and copying */
82 void	nmea_degrees(char *dst, char *src, int neg, size_t len);
83 #endif
84 
85 /* degrade the timedelta sensor */
86 void	nmea_timeout(void *);
87 
88 void
89 nmeaattach(int dummy)
90 {
91 }
92 
93 int
94 nmeaopen(dev_t dev, struct tty *tp)
95 {
96 	struct proc *p = curproc;
97 	struct nmea *np;
98 	int error;
99 
100 	if (tp->t_line == NMEADISC)
101 		return ENODEV;
102 	if ((error = suser(p, 0)) != 0)
103 		return error;
104 	np = malloc(sizeof(struct nmea), M_DEVBUF, M_WAITOK | M_ZERO);
105 	snprintf(np->timedev.xname, sizeof(np->timedev.xname), "nmea%d",
106 	    nmea_nxid++);
107 	nmea_count++;
108 	np->time.status = SENSOR_S_UNKNOWN;
109 	np->time.type = SENSOR_TIMEDELTA;
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 	strlcpy(np->signal.desc, "Signal", sizeof(np->signal.desc));
116 	sensor_attach(&np->timedev, &np->signal);
117 
118 	np->sync = 1;
119 	tp->t_sc = (caddr_t)np;
120 
121 	error = linesw[TTYDISC].l_open(dev, tp);
122 	if (error) {
123 		free(np, M_DEVBUF);
124 		tp->t_sc = NULL;
125 	} else {
126 		sensordev_install(&np->timedev);
127 		timeout_set(&np->nmea_tout, nmea_timeout, np);
128 	}
129 	return error;
130 }
131 
132 int
133 nmeaclose(struct tty *tp, int flags)
134 {
135 	struct nmea *np = (struct nmea *)tp->t_sc;
136 
137 	tp->t_line = TTYDISC;	/* switch back to termios */
138 	timeout_del(&np->nmea_tout);
139 	sensordev_deinstall(&np->timedev);
140 	free(np, M_DEVBUF);
141 	tp->t_sc = NULL;
142 	nmea_count--;
143 	if (nmea_count == 0)
144 		nmea_nxid = 0;
145 	return linesw[TTYDISC].l_close(tp, flags);
146 }
147 
148 /* Collect NMEA sentences from the tty. */
149 int
150 nmeainput(int c, struct tty *tp)
151 {
152 	struct nmea *np = (struct nmea *)tp->t_sc;
153 	struct timespec ts;
154 	int64_t gap;
155 	long tmin, tmax;
156 
157 	switch (c) {
158 	case '$':
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 
173 #ifdef NMEA_DEBUG
174 		if (nmeadebug > 0) {
175 			linesw[TTYDISC].l_rint('[', tp);
176 			linesw[TTYDISC].l_rint('0' + np->gapno++, tp);
177 			linesw[TTYDISC].l_rint(']', tp);
178 		}
179 #endif
180 		np->gap = gap;
181 
182 		/*
183 		 * If a tty timestamp is available, make sure its value is
184 		 * reasonable by comparing against the timestamp just taken.
185 		 * If they differ by more than 2 seconds, assume no PPS signal
186 		 * is present, note the fact, and keep using the timestamp
187 		 * value.  When this happens, the sensor state is set to
188 		 * CRITICAL later when the GPRMC sentence is decoded.
189 		 */
190 		if (tp->t_flags & (TS_TSTAMPDCDSET | TS_TSTAMPDCDCLR |
191 		    TS_TSTAMPCTSSET | TS_TSTAMPCTSCLR)) {
192 			tmax = lmax(np->ts.tv_sec, tp->t_tv.tv_sec);
193 			tmin = lmin(np->ts.tv_sec, tp->t_tv.tv_sec);
194 			if (tmax - tmin > 1)
195 				np->no_pps = 1;
196 			else {
197 				np->ts.tv_sec = tp->t_tv.tv_sec;
198 				np->ts.tv_nsec = tp->t_tv.tv_usec *
199 				    1000L;
200 				np->no_pps = 0;
201 			}
202 		}
203 		break;
204 	case '\r':
205 	case '\n':
206 		if (!np->sync) {
207 			np->cbuf[np->pos] = '\0';
208 			nmea_scan(np, tp);
209 			np->sync = 1;
210 		}
211 		break;
212 	default:
213 		if (!np->sync && np->pos < (NMEAMAX - 1))
214 			np->cbuf[np->pos++] = c;
215 		break;
216 	}
217 	/* pass data to termios */
218 	return linesw[TTYDISC].l_rint(c, tp);
219 }
220 
221 /* Scan the NMEA sentence just received. */
222 void
223 nmea_scan(struct nmea *np, struct tty *tp)
224 {
225 	int fldcnt = 0, cksum = 0, msgcksum, n;
226 	char *fld[MAXFLDS], *cs;
227 
228 	/* split into fields and calculate the checksum */
229 	fld[fldcnt++] = &np->cbuf[0];	/* message type */
230 	for (cs = NULL, n = 0; n < np->pos && cs == NULL; n++) {
231 		switch (np->cbuf[n]) {
232 		case '*':
233 			np->cbuf[n] = '\0';
234 			cs = &np->cbuf[n + 1];
235 			break;
236 		case ',':
237 			if (fldcnt < MAXFLDS) {
238 				cksum ^= np->cbuf[n];
239 				np->cbuf[n] = '\0';
240 				fld[fldcnt++] = &np->cbuf[n + 1];
241 			} else {
242 				DPRINTF(("nr of fields in %s sentence exceeds "
243 				    "maximum of %d\n", fld[0], MAXFLDS));
244 				return;
245 			}
246 			break;
247 		default:
248 			cksum ^= np->cbuf[n];
249 		}
250 	}
251 
252 	/* we only look at the GPRMC message */
253 	if (strcmp(fld[0], "GPRMC"))
254 		return;
255 
256 	/* if we have a checksum, verify it */
257 	if (cs != NULL) {
258 		msgcksum = 0;
259 		while (*cs) {
260 			if ((*cs >= '0' && *cs <= '9') ||
261 			    (*cs >= 'A' && *cs <= 'F')) {
262 				if (msgcksum)
263 					msgcksum <<= 4;
264 				if (*cs >= '0' && *cs<= '9')
265 					msgcksum += *cs - '0';
266 				else if (*cs >= 'A' && *cs <= 'F')
267 					msgcksum += 10 + *cs - 'A';
268 				cs++;
269 			} else {
270 				DPRINTF(("bad char %c in checksum\n", *cs));
271 				return;
272 			}
273 		}
274 		if (msgcksum != cksum) {
275 			DPRINTF(("checksum mismatch\n"));
276 			return;
277 		}
278 	}
279 	nmea_gprmc(np, tp, fld, fldcnt);
280 }
281 
282 /* Decode the recommended minimum specific GPS/TRANSIT data. */
283 void
284 nmea_gprmc(struct nmea *np, struct tty *tp, char *fld[], int fldcnt)
285 {
286 	int64_t date_nano, time_nano, nmea_now;
287 
288 	if (fldcnt != 12 && fldcnt != 13) {
289 		DPRINTF(("gprmc: field count mismatch, %d\n", fldcnt));
290 		return;
291 	}
292 	if (nmea_time_to_nano(fld[1], &time_nano)) {
293 		DPRINTF(("gprmc: illegal time, %s\n", fld[1]));
294 		return;
295 	}
296 	if (nmea_date_to_nano(fld[9], &date_nano)) {
297 		DPRINTF(("gprmc: illegal date, %s\n", fld[9]));
298 		return;
299 	}
300 	nmea_now = date_nano + time_nano;
301 	if (nmea_now <= np->last) {
302 		DPRINTF(("gprmc: time not monotonically increasing\n"));
303 		return;
304 	}
305 	np->last = nmea_now;
306 	np->gap = 0LL;
307 #ifdef NMEA_DEBUG
308 	if (np->time.status == SENSOR_S_UNKNOWN) {
309 		np->time.status = SENSOR_S_OK;
310 		timeout_add_sec(&np->nmea_tout, TRUSTTIME);
311 	}
312 	np->gapno = 0;
313 	if (nmeadebug > 0) {
314 		linesw[TTYDISC].l_rint('[', tp);
315 		linesw[TTYDISC].l_rint('C', tp);
316 		linesw[TTYDISC].l_rint(']', tp);
317 	}
318 #endif
319 
320 	np->time.value = np->ts.tv_sec * 1000000000LL +
321 	    np->ts.tv_nsec - nmea_now;
322 	np->time.tv.tv_sec = np->ts.tv_sec;
323 	np->time.tv.tv_usec = np->ts.tv_nsec / 1000L;
324 
325 	if (fldcnt != 13)
326 		strlcpy(np->time.desc, "GPS", sizeof(np->time.desc));
327 	else if (fldcnt == 13 && *fld[12] != np->mode) {
328 		np->mode = *fld[12];
329 		switch (np->mode) {
330 		case 'S':
331 			strlcpy(np->time.desc, "GPS simulated",
332 			    sizeof(np->time.desc));
333 			break;
334 		case 'E':
335 			strlcpy(np->time.desc, "GPS estimated",
336 			    sizeof(np->time.desc));
337 			break;
338 		case 'A':
339 			strlcpy(np->time.desc, "GPS autonomous",
340 			    sizeof(np->time.desc));
341 			break;
342 		case 'D':
343 			strlcpy(np->time.desc, "GPS differential",
344 			    sizeof(np->time.desc));
345 			break;
346 		case 'N':
347 			strlcpy(np->time.desc, "GPS invalid",
348 			    sizeof(np->time.desc));
349 			break;
350 		default:
351 			strlcpy(np->time.desc, "GPS unknown",
352 			    sizeof(np->time.desc));
353 			DPRINTF(("gprmc: unknown mode '%c'\n", np->mode));
354 		}
355 	}
356 #if NMEA_POS_IN_DESC
357 	nmea_degrees(np->time.desc, fld[3], *fld[4] == 'S' ? 1 : 0,
358 	    sizeof(np->time.desc));
359 	nmea_degrees(np->time.desc, fld[5], *fld[6] == 'W' ? 1 : 0,
360 	    sizeof(np->time.desc));
361 #endif
362 	switch (*fld[2]) {
363 	case 'A':	/* The GPS has a fix, (re)arm the timeout. */
364 		np->time.status = SENSOR_S_OK;
365 		np->signal.status = SENSOR_S_OK;
366 		timeout_add_sec(&np->nmea_tout, TRUSTTIME);
367 		break;
368 	case 'V':	/*
369 			 * The GPS indicates a warning status, do not add to
370 			 * the timeout, if the condition persist, the sensor
371 			 * will be degraded.  Signal the condition through
372 			 * the signal sensor.
373 			 */
374 		np->signal.status = SENSOR_S_WARN;
375 		break;
376 	}
377 
378 	/*
379 	 * If tty timestamping is requested, but no PPS signal is present, set
380 	 * the sensor state to CRITICAL.
381 	 */
382 	if (np->no_pps)
383 		np->time.status = SENSOR_S_CRIT;
384 }
385 
386 #ifdef NMEA_POS_IN_DESC
387 /* format a nmea position in the form DDDMM.MMMM to DDDdMM.MMm */
388 void
389 nmea_degrees(char *dst, char *src, int neg, size_t len)
390 {
391 	size_t dlen, ppos, rlen;
392 	int n;
393 	char *p;
394 
395 	for (dlen = 0; *dst; dlen++)
396 		dst++;
397 
398 	while (*src == '0')
399 		++src;	/* skip leading zeroes */
400 
401 	for (p = src, ppos = 0; *p; ppos++)
402 		if (*p++ == '.')
403 			break;
404 
405 	if (*p == '\0')
406 		return;	/* no decimal point */
407 
408 	/*
409 	 * we need at least room for a comma, an optional '-', the src data up
410 	 * to the decimal point, the decimal point itself, two digits after
411 	 * it and some additional characters:  an optional leading '0' in case
412 	 * there a no degrees in src, the 'd' degrees indicator, the 'm'
413 	 * minutes indicator and the terminating NUL character.
414 	 */
415 	rlen = dlen + ppos + 7;
416 	if (neg)
417 		rlen++;
418 	if (ppos < 3)
419 		rlen++;
420 	if (len < rlen)
421 		return;		/* not enough room in dst */
422 
423 	*dst++ = ',';
424 	if (neg)
425 		*dst++ = '-';
426 
427 	if (ppos < 3)
428 		*dst++ = '0';
429 
430 	for (n = 0; *src && n + 2 < ppos; n++)
431 		*dst++ = *src++;
432 	*dst++ = 'd';
433 	if (ppos == 0)
434 		*dst++ = '0';
435 
436 	for (; *src && n < (ppos + 3); n++)
437 		*dst++ = *src++;
438 	*dst++ = 'm';
439 	*dst = '\0';
440 }
441 #endif
442 
443 /*
444  * Convert a NMEA 0183 formatted date string to seconds since the epoch.
445  * The string must be of the form DDMMYY.
446  * Return 0 on success, -1 if illegal characters are encountered.
447  */
448 int
449 nmea_date_to_nano(char *s, int64_t *nano)
450 {
451 	struct clock_ymdhms ymd;
452 	time_t secs;
453 	char *p;
454 	int n;
455 
456 	/* make sure the input contains only numbers and is six digits long */
457 	for (n = 0, p = s; n < 6 && *p && *p >= '0' && *p <= '9'; n++, p++)
458 		;
459 	if (n != 6 || (*p != '\0'))
460 		return -1;
461 
462 	ymd.dt_year = 2000 + (s[4] - '0') * 10 + (s[5] - '0');
463 	ymd.dt_mon = (s[2] - '0') * 10 + (s[3] - '0');
464 	ymd.dt_day = (s[0] - '0') * 10 + (s[1] - '0');
465 	ymd.dt_hour = ymd.dt_min = ymd.dt_sec = 0;
466 
467 	secs = clock_ymdhms_to_secs(&ymd);
468 	*nano = secs * 1000000000LL;
469 	return 0;
470 }
471 
472 /*
473  * Convert NMEA 0183 formatted time string to nanoseconds since midnight.
474  * The string must be of the form HHMMSS[.[sss]] (e.g. 143724 or 143723.615).
475  * Return 0 on success, -1 if illegal characters are encountered.
476  */
477 int
478 nmea_time_to_nano(char *s, int64_t *nano)
479 {
480 	long fac = 36000L, div = 6L, secs = 0L, frac = 0L;
481 	char ul = '2';
482 	int n;
483 
484 	for (n = 0, secs = 0; fac && *s && *s >= '0' && *s <= ul; s++, n++) {
485 		secs += (*s - '0') * fac;
486 		div = 16 - div;
487 		fac /= div;
488 		switch (n) {
489 		case 0:
490 			if (*s <= '1')
491 				ul = '9';
492 			else
493 				ul = '3';
494 			break;
495 		case 1:
496 		case 3:
497 			ul = '5';
498 			break;
499 		case 2:
500 		case 4:
501 			ul = '9';
502 			break;
503 		}
504 	}
505 	if (fac)
506 		return -1;
507 
508 	/* Handle the fractions of a second, up to a maximum of 6 digits. */
509 	div = 1L;
510 	if (*s == '.') {
511 		for (++s; div < 1000000 && *s && *s >= '0' && *s <= '9'; s++) {
512 			frac *= 10;
513 			frac += (*s - '0');
514 			div *= 10;
515 		}
516 	}
517 
518 	if (*s != '\0')
519 		return -1;
520 
521 	*nano = secs * 1000000000LL + (int64_t)frac * (1000000000 / div);
522 	return 0;
523 }
524 
525 /*
526  * Degrade the sensor state if we received no NMEA sentences for more than
527  * TRUSTTIME seconds.
528  */
529 void
530 nmea_timeout(void *xnp)
531 {
532 	struct nmea *np = xnp;
533 
534 	if (np->time.status == SENSOR_S_OK) {
535 		np->time.status = SENSOR_S_WARN;
536 		/*
537 		 * further degrade in TRUSTTIME seconds if no new valid NMEA
538 		 * sentences are received.
539 		 */
540 		timeout_add_sec(&np->nmea_tout, TRUSTTIME);
541 	} else
542 		np->time.status = SENSOR_S_CRIT;
543 }
544