Line data Source code
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 0 : mstsattach(int dummy)
80 : {
81 0 : }
82 :
83 : int
84 0 : mstsopen(dev_t dev, struct tty *tp, struct proc *p)
85 : {
86 : struct msts *np;
87 : int error;
88 :
89 : DPRINTF(("mstsopen\n"));
90 0 : if (tp->t_line == MSTSDISC)
91 0 : return ENODEV;
92 0 : if ((error = suser(p)) != 0)
93 0 : return error;
94 0 : np = malloc(sizeof(struct msts), M_DEVBUF, M_WAITOK|M_ZERO);
95 0 : snprintf(np->timedev.xname, sizeof(np->timedev.xname), "msts%d",
96 0 : msts_nxid++);
97 0 : msts_count++;
98 0 : np->time.status = SENSOR_S_UNKNOWN;
99 0 : np->time.type = SENSOR_TIMEDELTA;
100 : #ifndef MSTS_DEBUG
101 0 : np->time.flags = SENSOR_FINVALID;
102 : #endif
103 0 : sensor_attach(&np->timedev, &np->time);
104 :
105 0 : np->signal.type = SENSOR_PERCENT;
106 0 : np->signal.status = SENSOR_S_UNKNOWN;
107 0 : np->signal.value = 100000LL;
108 0 : strlcpy(np->signal.desc, "Signal", sizeof(np->signal.desc));
109 0 : sensor_attach(&np->timedev, &np->signal);
110 :
111 0 : np->sync = 1;
112 0 : tp->t_sc = (caddr_t)np;
113 :
114 0 : error = linesw[TTYDISC].l_open(dev, tp, p);
115 0 : if (error) {
116 0 : free(np, M_DEVBUF, sizeof(*np));
117 0 : tp->t_sc = NULL;
118 0 : } else {
119 0 : sensordev_install(&np->timedev);
120 0 : timeout_set(&np->msts_tout, msts_timeout, np);
121 : }
122 :
123 0 : return error;
124 0 : }
125 :
126 : int
127 0 : mstsclose(struct tty *tp, int flags, struct proc *p)
128 : {
129 0 : struct msts *np = (struct msts *)tp->t_sc;
130 :
131 0 : tp->t_line = TTYDISC; /* switch back to termios */
132 0 : timeout_del(&np->msts_tout);
133 0 : sensordev_deinstall(&np->timedev);
134 0 : free(np, M_DEVBUF, sizeof(*np));
135 0 : tp->t_sc = NULL;
136 0 : msts_count--;
137 0 : if (msts_count == 0)
138 0 : msts_nxid = 0;
139 0 : return linesw[TTYDISC].l_close(tp, flags, p);
140 : }
141 :
142 : /* collect MSTS sentence from tty */
143 : int
144 0 : mstsinput(int c, struct tty *tp)
145 : {
146 0 : struct msts *np = (struct msts *)tp->t_sc;
147 0 : struct timespec ts;
148 : int64_t gap;
149 : long tmin, tmax;
150 :
151 0 : switch (c) {
152 : case 2: /* ASCII <STX> */
153 0 : nanotime(&ts);
154 0 : np->pos = np->sync = 0;
155 0 : gap = (ts.tv_sec * 1000000000LL + ts.tv_nsec) -
156 0 : (np->lts.tv_sec * 1000000000LL + np->lts.tv_nsec);
157 :
158 0 : np->lts.tv_sec = ts.tv_sec;
159 0 : np->lts.tv_nsec = ts.tv_nsec;
160 :
161 0 : if (gap <= np->gap)
162 : break;
163 :
164 0 : np->ts.tv_sec = ts.tv_sec;
165 0 : np->ts.tv_nsec = ts.tv_nsec;
166 0 : 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 0 : if (tp->t_flags & (TS_TSTAMPDCDSET | TS_TSTAMPDCDCLR |
177 : TS_TSTAMPCTSSET | TS_TSTAMPCTSCLR)) {
178 0 : tmax = lmax(np->ts.tv_sec, tp->t_tv.tv_sec);
179 0 : tmin = lmin(np->ts.tv_sec, tp->t_tv.tv_sec);
180 0 : if (tmax - tmin > 1)
181 0 : np->no_pps = 1;
182 : else {
183 0 : np->ts.tv_sec = tp->t_tv.tv_sec;
184 0 : np->ts.tv_nsec = tp->t_tv.tv_usec *
185 : 1000L;
186 0 : np->no_pps = 0;
187 : }
188 : }
189 : break;
190 : case 3: /* ASCII <ETX> */
191 0 : if (!np->sync) {
192 0 : np->cbuf[np->pos] = '\0';
193 0 : msts_scan(np, tp);
194 0 : np->sync = 1;
195 0 : }
196 : break;
197 : default:
198 0 : if (!np->sync && np->pos < (MSTSMAX - 1))
199 0 : np->cbuf[np->pos++] = c;
200 : break;
201 : }
202 : /* pass data to termios */
203 0 : return linesw[TTYDISC].l_rint(c, tp);
204 0 : }
205 :
206 : /* Scan the MSTS sentence just received */
207 : void
208 0 : msts_scan(struct msts *np, struct tty *tp)
209 : {
210 : int fldcnt = 0, n;
211 0 : char *fld[MAXFLDS], *cs;
212 :
213 : /* split into fields */
214 0 : fld[fldcnt++] = &np->cbuf[0];
215 0 : for (cs = NULL, n = 0; n < np->pos && cs == NULL; n++) {
216 0 : switch (np->cbuf[n]) {
217 : case 3: /* ASCII <ETX> */
218 0 : np->cbuf[n] = '\0';
219 0 : cs = &np->cbuf[n + 1];
220 0 : break;
221 : case ';':
222 0 : if (fldcnt < MAXFLDS) {
223 0 : np->cbuf[n] = '\0';
224 0 : fld[fldcnt++] = &np->cbuf[n + 1];
225 : } else {
226 : DPRINTF(("nr of fields in sentence exceeds "
227 : "maximum of %d\n", MAXFLDS));
228 0 : return;
229 : }
230 0 : break;
231 : }
232 : }
233 0 : msts_decode(np, tp, fld, fldcnt);
234 0 : }
235 :
236 : /* Decode the time string */
237 : void
238 0 : msts_decode(struct msts *np, struct tty *tp, char *fld[], int fldcnt)
239 : {
240 0 : int64_t date_nano, time_nano, msts_now;
241 : int jumped = 0;
242 :
243 0 : if (fldcnt != MAXFLDS) {
244 : DPRINTF(("msts: field count mismatch, %d\n", fldcnt));
245 0 : return;
246 : }
247 0 : if (msts_time_to_nano(fld[2], &time_nano)) {
248 : DPRINTF(("msts: illegal time, %s\n", fld[2]));
249 0 : return;
250 : }
251 0 : if (msts_date_to_nano(fld[0], &date_nano)) {
252 : DPRINTF(("msts: illegal date, %s\n", fld[0]));
253 0 : return;
254 : }
255 0 : msts_now = date_nano + time_nano;
256 0 : if ( fld[3][2] == ' ' ) /* received time in CET */
257 0 : msts_now = msts_now - 3600 * 1000000000LL;
258 0 : if ( fld[3][2] == 'S' ) /* received time in CEST */
259 0 : msts_now = msts_now - 2 * 3600 * 1000000000LL;
260 0 : if (msts_now <= np->last) {
261 : DPRINTF(("msts: time not monotonically increasing\n"));
262 : jumped = 1;
263 0 : }
264 0 : np->last = msts_now;
265 0 : 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 0 : np->time.value = np->ts.tv_sec * 1000000000LL +
274 0 : np->ts.tv_nsec - msts_now;
275 0 : np->time.tv.tv_sec = np->ts.tv_sec;
276 0 : np->time.tv.tv_usec = np->ts.tv_nsec / 1000L;
277 0 : if (np->time.status == SENSOR_S_UNKNOWN) {
278 0 : np->time.status = SENSOR_S_OK;
279 0 : np->time.flags &= ~SENSOR_FINVALID;
280 0 : strlcpy(np->time.desc, "MSTS", sizeof(np->time.desc));
281 0 : }
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 0 : if (fld[3][0] == ' ' && fld[3][1] == ' ') {
290 0 : np->time.status = SENSOR_S_OK;
291 0 : np->signal.status = SENSOR_S_OK;
292 0 : } else
293 0 : np->signal.status = SENSOR_S_WARN;
294 :
295 0 : if (jumped)
296 0 : np->time.status = SENSOR_S_WARN;
297 0 : if (np->time.status == SENSOR_S_OK)
298 0 : 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 0 : if (np->no_pps)
305 0 : np->time.status = SENSOR_S_CRIT;
306 0 : }
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 0 : msts_date_to_nano(char *s, int64_t *nano)
315 : {
316 0 : struct clock_ymdhms ymd;
317 : time_t secs;
318 : char *p;
319 : int n;
320 :
321 0 : if (s[0] != 'D' || s[1] != ':' || s[4] != '.' || s[7] != '.')
322 0 : return -1;
323 :
324 : /* shift numbers to DDMMYY */
325 0 : s[0]=s[2];
326 0 : s[1]=s[3];
327 0 : s[2]=s[5];
328 0 : s[3]=s[6];
329 0 : s[4]=s[8];
330 0 : s[5]=s[9];
331 0 : s[6]='\0';
332 :
333 : /* make sure the input contains only numbers and is six digits long */
334 0 : for (n = 0, p = s; n < 6 && *p && *p >= '0' && *p <= '9'; n++, p++)
335 : ;
336 0 : if (n != 6 || (*p != '\0'))
337 0 : return -1;
338 :
339 0 : ymd.dt_year = 2000 + (s[4] - '0') * 10 + (s[5] - '0');
340 0 : ymd.dt_mon = (s[2] - '0') * 10 + (s[3] - '0');
341 0 : ymd.dt_day = (s[0] - '0') * 10 + (s[1] - '0');
342 0 : ymd.dt_hour = ymd.dt_min = ymd.dt_sec = 0;
343 :
344 0 : secs = clock_ymdhms_to_secs(&ymd);
345 0 : *nano = secs * 1000000000LL;
346 0 : return 0;
347 0 : }
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 0 : 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 0 : if (s[0] != 'U' || s[1] != ':' || s[4] != '.' || s[7] != '.')
362 0 : return -1;
363 :
364 : /* shift numbers to HHMMSS */
365 0 : s[0]=s[2];
366 0 : s[1]=s[3];
367 0 : s[2]=s[5];
368 0 : s[3]=s[6];
369 0 : s[4]=s[8];
370 0 : s[5]=s[9];
371 0 : s[6]='\0';
372 :
373 0 : for (n = 0, secs = 0; fac && *s && *s >= '0' && *s <= ul; s++, n++) {
374 0 : secs += (*s - '0') * fac;
375 0 : div = 16 - div;
376 0 : fac /= div;
377 0 : switch (n) {
378 : case 0:
379 0 : if (*s <= '1')
380 0 : ul = '9';
381 : else
382 : ul = '3';
383 : break;
384 : case 1:
385 : case 3:
386 : ul = '5';
387 0 : break;
388 : case 2:
389 : case 4:
390 : ul = '9';
391 0 : break;
392 : }
393 : }
394 0 : if (fac)
395 0 : return -1;
396 :
397 0 : if (*s != '\0')
398 0 : return -1;
399 :
400 0 : *nano = secs * 1000000000LL;
401 0 : return 0;
402 0 : }
403 :
404 : /*
405 : * Degrade the sensor state if we received no MSTS string for more than
406 : * TRUSTTIME seconds.
407 : */
408 : void
409 0 : msts_timeout(void *xnp)
410 : {
411 0 : struct msts *np = xnp;
412 :
413 0 : if (np->time.status == SENSOR_S_OK) {
414 0 : np->time.status = SENSOR_S_WARN;
415 : /*
416 : * further degrade in TRUSTTIME seconds if no new valid MSTS
417 : * strings are received.
418 : */
419 0 : timeout_add_sec(&np->msts_tout, TRUSTTIME);
420 0 : } else
421 0 : np->time.status = SENSOR_S_CRIT;
422 0 : }
|