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
mstsattach(int dummy)79 mstsattach(int dummy)
80 {
81 }
82
83 int
mstsopen(dev_t dev,struct tty * tp,struct proc * p)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
mstsclose(struct tty * tp,int flags,struct proc * p)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
mstsinput(int c,struct tty * tp)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
msts_scan(struct msts * np,struct tty * tp)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
msts_decode(struct msts * np,struct tty * tp,char * fld[],int fldcnt)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
msts_date_to_nano(char * s,int64_t * nano)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
msts_time_to_nano(char * s,int64_t * nano)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
msts_timeout(void * xnp)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