/src/gpsd/gpsd-3.26.2~dev/gpsd/serial.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * This file is Copyright by the GPSD project |
3 | | * SPDX-License-Identifier: BSD-2-clause |
4 | | */ |
5 | | |
6 | | #include "../include/gpsd_config.h" // must be before all includes |
7 | | |
8 | | #include <ctype.h> // for isdigit() |
9 | | #include <dirent.h> // for DIR |
10 | | #include <errno.h> |
11 | | #include <fcntl.h> |
12 | | #ifdef HAVE_LINUX_SERIAL_H |
13 | | #include <linux/serial.h> |
14 | | #endif |
15 | | #include <stdbool.h> |
16 | | #include <stdio.h> |
17 | | #include <stdlib.h> // for realpath() |
18 | | #include <string.h> |
19 | | #include <sys/ioctl.h> |
20 | | #include <sys/param.h> // defines BSD |
21 | | #include <sys/socket.h> |
22 | | #include <sys/stat.h> |
23 | | #include <sys/types.h> |
24 | | #include <termios.h> |
25 | | #include <unistd.h> |
26 | | |
27 | | #ifdef HAVE_SYS_SYSMACROS_H |
28 | | #include <sys/sysmacros.h> // defines major() |
29 | | #endif // HAVE_SYS_SYSMACROS_H |
30 | | |
31 | | #ifdef ENABLE_BLUEZ |
32 | | #include <bluetooth/bluetooth.h> |
33 | | #include <bluetooth/hci.h> |
34 | | #include <bluetooth/hci_lib.h> |
35 | | #include <bluetooth/rfcomm.h> |
36 | | #endif // ENABLE_BLUEZ |
37 | | |
38 | | #include "../include/compiler.h" // for FALLTHROUGH |
39 | | #include "../include/gpsd.h" |
40 | | |
41 | | // Workaround for HP-UX 11.23, which is missing CRTSCTS |
42 | | #ifndef CRTSCTS |
43 | | # ifdef CNEW_RTSCTS |
44 | | # define CRTSCTS CNEW_RTSCTS |
45 | | # else |
46 | | # define CRTSCTS 0 |
47 | | # endif // CNEW_RTSCTS |
48 | | #endif // !CRTSCTS |
49 | | |
50 | | // soupcetypes |
51 | | static struct vlist_t sourcetypes[] = { |
52 | | {SOURCE_UNKNOWN, "Unknown"}, |
53 | | {SOURCE_BLOCKDEV, "Blockdev"}, |
54 | | {SOURCE_RS232, "Serial"}, |
55 | | {SOURCE_USB, "USB"}, |
56 | | {SOURCE_BLUETOOTH, "Bluetooth"}, |
57 | | {SOURCE_CAN, "Canbus"}, |
58 | | {SOURCE_PTY, "PTY"}, |
59 | | {SOURCE_TCP, "TCP"}, |
60 | | {SOURCE_UDP, "UDP"}, |
61 | | {SOURCE_GPSD, "GPSD JSON"}, |
62 | | {SOURCE_PPS, "PPS"}, |
63 | | {SOURCE_PIPE, "PIPE"}, |
64 | | {SOURCE_ACM, "ACM"}, |
65 | | {0, NULL}, |
66 | | }; |
67 | | |
68 | | // figure out what kind of device we're looking at |
69 | | static sourcetype_t gpsd_classify(struct gps_device_t *session) |
70 | 0 | { |
71 | 0 | struct stat sb; |
72 | 0 | const char *path = session->gpsdata.dev.path; |
73 | |
|
74 | 0 | if (-1 == stat(path, &sb)) { |
75 | 0 | GPSD_LOG(LOG_ERROR, &session->context->errout, |
76 | 0 | "SER: stat(%s) failed: %s(%d)\n", |
77 | 0 | session->gpsdata.dev.path, strerror(errno), errno); |
78 | 0 | return SOURCE_UNKNOWN; |
79 | 0 | } |
80 | 0 | switch (sb.st_mode & S_IFMT) { |
81 | 0 | case S_IFBLK: |
82 | | // block device |
83 | 0 | return SOURCE_BLOCKDEV; |
84 | 0 | case S_IFCHR: |
85 | | // some char device |
86 | 0 | break; |
87 | 0 | case S_IFDIR: |
88 | | // directory?? |
89 | 0 | return SOURCE_UNKNOWN; |
90 | 0 | case S_IFIFO: |
91 | 0 | return SOURCE_PIPE; |
92 | 0 | case S_IFLNK: |
93 | | // symlink ?!, assume char?? |
94 | 0 | break; |
95 | 0 | case S_IFREG: |
96 | | // regular file |
97 | 0 | return SOURCE_BLOCKDEV; |
98 | 0 | case S_IFSOCK: |
99 | | // this assumes we won't get UDP from a filesystem socket |
100 | 0 | return SOURCE_TCP; |
101 | 0 | default: |
102 | | // huh?? |
103 | 0 | GPSD_LOG(LOG_WARNING, &session->context->errout, |
104 | 0 | "SER: gpsd_classify(%s) Unknown st_mode %d\n", |
105 | 0 | path, sb.st_mode & S_IFMT); |
106 | 0 | break; |
107 | 0 | } |
108 | | |
109 | | // OS-independent check for ptys using Unix98 naming convention |
110 | 0 | if (0 == strncmp(path, "/dev/pts/", 9)) { |
111 | 0 | return SOURCE_PTY; |
112 | 0 | } |
113 | | |
114 | | // some more direct way to check for PPS? |
115 | 0 | if (0 == strncmp(path, "/dev/pps", 8)) { |
116 | 0 | return SOURCE_PPS; |
117 | 0 | } |
118 | | |
119 | 0 | if (S_ISCHR(sb.st_mode)) { |
120 | 0 | sourcetype_t devtype = SOURCE_RS232; |
121 | 0 | #ifdef __linux__ |
122 | | /* Linux major device numbers live here |
123 | | * |
124 | | * https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/admin-guide/devices.txt |
125 | | * |
126 | | * Note: This code works because Linux major device numbers are |
127 | | * stable and architecture-independent. It is *not* a good model |
128 | | * for other Unixes where either or both assumptions may break. |
129 | | */ |
130 | 0 | int devmajor = major(sb.st_rdev); |
131 | 0 | int devminor = minor(sb.st_rdev); |
132 | |
|
133 | 0 | switch (devmajor) { |
134 | 0 | case 3: // First MFM, RLL and IDE hard disk/CD-ROM interface ? |
135 | 0 | FALLTHROUGH |
136 | 0 | case 136: // Unix98 PTY slaves |
137 | 0 | FALLTHROUGH |
138 | 0 | case 137: // Unix98 PTY slaves |
139 | 0 | FALLTHROUGH |
140 | 0 | case 138: // Unix98 PTY slaves |
141 | 0 | FALLTHROUGH |
142 | 0 | case 139: // Unix98 PTY slaves |
143 | 0 | FALLTHROUGH |
144 | 0 | case 140: // Unix98 PTY slaves |
145 | 0 | FALLTHROUGH |
146 | 0 | case 141: // Unix98 PTY slaves |
147 | 0 | FALLTHROUGH |
148 | 0 | case 142: // Unix98 PTY slaves |
149 | 0 | FALLTHROUGH |
150 | 0 | case 143: // Unix98 PTY slaves |
151 | 0 | devtype = SOURCE_PTY; |
152 | 0 | break; |
153 | | |
154 | 0 | case 4: // TTY Devices |
155 | 0 | FALLTHROUGH |
156 | 0 | case 22: // Digiboard serial card |
157 | 0 | FALLTHROUGH |
158 | 0 | case 23: // Digiboard serial card |
159 | 0 | FALLTHROUGH |
160 | 0 | case 24: // Stallion serial card |
161 | 0 | FALLTHROUGH |
162 | 0 | case 32: // Specialx serial card |
163 | 0 | FALLTHROUGH |
164 | 0 | case 46: // Rocketport serial card |
165 | 0 | FALLTHROUGH |
166 | 0 | case 48: // SDL RISCom serial card |
167 | 0 | FALLTHROUGH |
168 | 0 | case 57: // Hayes ESP serial card |
169 | 0 | FALLTHROUGH |
170 | 0 | case 71: // Computone IntelliPort II serial card |
171 | 0 | FALLTHROUGH |
172 | 0 | case 75: // Specialix IO8+ serial card |
173 | 0 | FALLTHROUGH |
174 | 0 | case 105: // Comtrol VS-1000+ serial card |
175 | 0 | FALLTHROUGH |
176 | 0 | case 112: // ISI serial card |
177 | 0 | FALLTHROUGH |
178 | 0 | case 148: // TCL serial card |
179 | 0 | FALLTHROUGH |
180 | 0 | case 154: // Specialix RIO serial card |
181 | 0 | FALLTHROUGH |
182 | 0 | case 156: // Specialix RIO serial card |
183 | 0 | FALLTHROUGH |
184 | 0 | case 164: // Chase Research serial card |
185 | 0 | FALLTHROUGH |
186 | 0 | case 172: // Moxa Intellio serial card |
187 | 0 | FALLTHROUGH |
188 | 0 | case 174: // SmartIO serial card |
189 | 0 | FALLTHROUGH |
190 | 0 | case 204: // Low-density serial ports |
191 | 0 | FALLTHROUGH |
192 | 0 | case 207: // 207 FREESCALE I.MX UARTS (TTYMXC*) |
193 | 0 | FALLTHROUGH |
194 | 0 | case 208: // User space serial ports |
195 | 0 | FALLTHROUGH |
196 | 0 | case 210: // SBE serial ports |
197 | 0 | FALLTHROUGH |
198 | 0 | case 2224: // A2232 serial ports |
199 | 0 | devtype = SOURCE_RS232; |
200 | 0 | break; |
201 | | |
202 | 0 | case 10: // Non-serial mice, misc features |
203 | 0 | if (223 == devminor) { |
204 | 0 | devtype = SOURCE_PPS; |
205 | 0 | } // else WTF? |
206 | 0 | break; |
207 | | |
208 | 0 | case 166: // ACM USB modems |
209 | | // ACM has no speed, otherwise similar to SOURCE_USB |
210 | 0 | devtype = SOURCE_ACM; |
211 | 0 | break; |
212 | 0 | case 188: // USB serial converters |
213 | 0 | devtype = SOURCE_USB; |
214 | 0 | break; |
215 | | |
216 | 0 | case 216: // Bluetooth RFCOMM TTY devices |
217 | 0 | FALLTHROUGH |
218 | 0 | case 217: // Bluetooth RFCOMM TTY devices (alternate devices) |
219 | 0 | devtype = SOURCE_BLUETOOTH; |
220 | 0 | break; |
221 | | |
222 | 0 | default: |
223 | | /* 234 to 254 Reserved for dynamic assignment |
224 | | * 240 to 254 Reserved for local/experimental use |
225 | | * |
226 | | * reports that Buildroot may use major 241 for serial, |
227 | | * instead of its documented purpose (usbmon). */ |
228 | 0 | GPSD_LOG(LOG_WARNING, &session->context->errout, |
229 | 0 | "SER: gpsd_classify(%s) Unknown Major Device # %d\n", |
230 | 0 | path, devmajor); |
231 | | // Give up, default to rs232 |
232 | 0 | devtype = SOURCE_RS232; |
233 | 0 | break; |
234 | 0 | } |
235 | 0 | #endif // __linux__ |
236 | | /* |
237 | | * See http://nadeausoftware.com/articles/2012/01/c_c_tip_how_use_compiler_predefined_macros_detect_operating_system |
238 | | * for discussion how this works. Key graphs: |
239 | | * |
240 | | * Compilers for the old BSD base for these distributions |
241 | | * defined the __bsdi__ macro, but none of these distributions |
242 | | * define it now. This leaves no generic "BSD" macro defined |
243 | | * by the compiler itself, but all UNIX-style OSes provide a |
244 | | * <sys/param.h> file. On BSD distributions, and only on BSD |
245 | | * distributions, this file defines a BSD macro that's set to |
246 | | * the OS version. Checking for this generic macro is more |
247 | | * robust than looking for known BSD distributions with |
248 | | * __DragonFly__, __FreeBSD__, __NetBSD__, and __OpenBSD__ |
249 | | * macros. |
250 | | * |
251 | | * Apple's OSX for the Mac and iOS for iPhones and iPads are |
252 | | * based in part on a fork of FreeBSD distributed as |
253 | | * Darwin. As such, OSX and iOS also define the BSD macro |
254 | | * within <sys/param.h>. However, compilers for OSX, iOS, and |
255 | | * Darwin do not define __unix__. To detect all BSD OSes, |
256 | | * including OSX, iOS, and Darwin, use an #if/#endif that |
257 | | * checks for __unix__ along with __APPLE__ and __MACH__ (see |
258 | | * the later section on OSX and iOS). |
259 | | */ |
260 | | #ifdef BSD |
261 | | /* |
262 | | * Hacky check for pty, which is what really matters for avoiding |
263 | | * adaptive delay. |
264 | | */ |
265 | | if (0 == strncmp(path, "/dev/ttyp", 9) || |
266 | | 0 == strncmp(path, "/dev/ttyq", 9)) { |
267 | | devtype = SOURCE_PTY; |
268 | | } else if (0 == strncmp(path, "/dev/ttyU", 9) || |
269 | | 0 == strncmp(path, "/dev/dtyU", 9)) { |
270 | | devtype = SOURCE_USB; |
271 | | } |
272 | | // XXX bluetooth |
273 | | #endif // BSD |
274 | 0 | return devtype; |
275 | 0 | } |
276 | | |
277 | 0 | return SOURCE_UNKNOWN; |
278 | 0 | } |
279 | | |
280 | | #ifdef __linux__ |
281 | | |
282 | | /* Try to find how many times a file is open. 0 to n. |
283 | | * return -1 on failure |
284 | | */ |
285 | | static int fusercount(struct gps_device_t *session) |
286 | 0 | { |
287 | 0 | DIR *procd, *fdd; |
288 | 0 | struct dirent *procentry, *fdentry; |
289 | 0 | char procpath[GPS_PATH_MAX], fdpath[GPS_PATH_MAX], linkpath[GPS_PATH_MAX]; |
290 | 0 | char *fullpath = NULL; // what dev.path points to |
291 | 0 | int cnt = 0; |
292 | | |
293 | | // POSIX 2008 |
294 | 0 | fullpath = realpath(session->gpsdata.dev.path, NULL); |
295 | 0 | if (NULL == fullpath) { |
296 | | // Huh? |
297 | 0 | GPSD_LOG(LOG_ERROR, &session->context->errout, |
298 | 0 | "SER: fusercount(): realpath(%s) failed: %s(%d)\n", |
299 | 0 | session->gpsdata.dev.path, |
300 | 0 | strerror(errno), errno); |
301 | 0 | return -1; |
302 | 0 | } |
303 | | |
304 | 0 | if (NULL == (procd = opendir("/proc"))) { |
305 | 0 | free(fullpath); |
306 | 0 | GPSD_LOG(LOG_ERROR, &session->context->errout, |
307 | 0 | "SER: fusercount(): opendir(/proc) failed: %s(%d)\n", |
308 | 0 | strerror(errno), errno); |
309 | 0 | return -1; |
310 | 0 | } |
311 | | |
312 | | // debug |
313 | | // GPSD_LOG(LOG_SHOUT, &session->context->errout, |
314 | | // "SER: fusercount: path %s fullpath %s\n", |
315 | | // session->gpsdata.dev.path, fullpath); |
316 | | |
317 | 0 | while (NULL != (procentry = readdir(procd))) { |
318 | 0 | if (0 == isdigit(procentry->d_name[0])) { |
319 | | // skip non-precess entries |
320 | 0 | continue; |
321 | 0 | } |
322 | | // longest procentry->d_name I could find was 12 |
323 | 0 | (void)snprintf(procpath, sizeof(procpath), |
324 | 0 | "/proc/%.20s/fd/", procentry->d_name); |
325 | 0 | if (NULL == (fdd = opendir(procpath))) { |
326 | 0 | continue; |
327 | 0 | } |
328 | 0 | while (NULL != (fdentry = readdir(fdd))) { |
329 | 0 | ssize_t rd; |
330 | |
|
331 | 0 | if (0 == isdigit(fdentry->d_name[0])) { |
332 | | // skip . and .. |
333 | 0 | continue; |
334 | 0 | } |
335 | 0 | (void)strlcpy(fdpath, procpath, sizeof(fdpath)); |
336 | 0 | (void)strlcat(fdpath, fdentry->d_name, sizeof(fdpath)); |
337 | | |
338 | | // readlink does not always NUL terminate. |
339 | 0 | rd = readlink(fdpath, linkpath, sizeof(linkpath) - 1); |
340 | 0 | if (0 > rd) { |
341 | | // maybe access error, maybe too long, maybe... |
342 | 0 | continue; |
343 | 0 | } |
344 | | // pacify coverity by setting NUL |
345 | 0 | linkpath[rd] = '\0'; |
346 | 0 | if ('/' != linkpath[0]) { |
347 | | // not a full path |
348 | 0 | continue; |
349 | 0 | } |
350 | | // debug |
351 | | // GPSD_LOG(LOG_SHOUT, &session->context->errout, |
352 | | // "SER: fusercount: %s linkpath %s fullpath %s\n", |
353 | | // fdpath, linkpath, fullpath); |
354 | 0 | if (0 == strcmp(linkpath, fullpath)) { |
355 | 0 | ++cnt; |
356 | 0 | } |
357 | 0 | } |
358 | 0 | (void)closedir(fdd); |
359 | 0 | } |
360 | 0 | (void)closedir(procd); |
361 | 0 | GPSD_LOG(LOG_IO, &session->context->errout, |
362 | 0 | "SER: fusercount: path %s fullpath %s cnt %d\n", |
363 | 0 | session->gpsdata.dev.path, fullpath, cnt); |
364 | 0 | free(fullpath); |
365 | |
|
366 | 0 | return cnt; |
367 | 0 | } |
368 | | #endif // __linux__ |
369 | | |
370 | | // to be called on allocating a device |
371 | | void gpsd_tty_init(struct gps_device_t *session) |
372 | 0 | { |
373 | | // mark GPS fd closed and its baud rate unknown |
374 | 0 | session->gpsdata.gps_fd = -1; |
375 | 0 | session->saved_baud = -1; |
376 | 0 | session->zerokill = false; |
377 | 0 | session->reawake = (time_t)0; |
378 | 0 | } |
379 | | |
380 | | #if !defined(HAVE_CFMAKERAW) |
381 | | /* |
382 | | * Local implementation of cfmakeraw (which is not specified by |
383 | | * POSIX; see matching declaration in gpsd.h). |
384 | | * |
385 | | * Pasted from man page; added in serial.c arbitrarily |
386 | | */ |
387 | | void cfmakeraw(struct termios *termios_p) |
388 | | { |
389 | | termios_p->c_iflag &= |
390 | | ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON); |
391 | | termios_p->c_oflag &= ~OPOST; |
392 | | termios_p->c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); |
393 | | termios_p->c_cflag &= ~(CSIZE | PARENB); |
394 | | termios_p->c_cflag |= CS8; |
395 | | } |
396 | | #endif // !defined(HAVE_CFMAKERAW) |
397 | | |
398 | | // speed conversion table for speed2code() and code2speed() |
399 | | static struct speed_code_t { |
400 | | speed_t code; |
401 | | int speed; |
402 | | } const speed_code[] = { |
403 | | // 4800 is gpsd minimum |
404 | | // list must be sorted ascending speed; |
405 | | {B4800, 4800}, |
406 | | {B9600, 9600}, |
407 | | {B19200, 19200}, |
408 | | {B38400, 38400}, |
409 | | {B57600, 57600}, |
410 | | {B115200, 115200}, |
411 | | {B230400, 230400}, |
412 | | #ifdef B460800 |
413 | | {B460800, 460800}, |
414 | | #endif |
415 | | #ifdef B500000 |
416 | | {B500000, 500000}, |
417 | | #endif |
418 | | #ifdef B576000 |
419 | | {B576000, 576000}, |
420 | | #endif |
421 | | #ifdef B921600 |
422 | | {B921600, 921600}, |
423 | | #endif |
424 | | #ifdef B1000000 |
425 | | {B1000000, 1000000}, |
426 | | #endif |
427 | | #ifdef B1152000 |
428 | | {B1152000, 1152000}, |
429 | | #endif |
430 | | #ifdef B1500000 |
431 | | {B1500000, 1500000}, |
432 | | #endif |
433 | | #ifdef B2000000 |
434 | | {B2000000, 2000000}, |
435 | | #endif |
436 | | #ifdef B2500000 |
437 | | {B2500000, 2500000}, |
438 | | #endif |
439 | | #ifdef B3000000 |
440 | | {B3000000, 3000000}, |
441 | | #endif |
442 | | #ifdef B3500000 |
443 | | {B3500000, 3500000}, |
444 | | #endif |
445 | | #ifdef B4000000 |
446 | | {B4000000, 4000000}, |
447 | | #endif |
448 | | {B0, 0}, // flag for end of list |
449 | | }; |
450 | | |
451 | | // Convert speed code into speed |
452 | | static speed_t speed2code(const int speed) |
453 | 0 | { |
454 | 0 | speed_t code = B9600; // fall back |
455 | 0 | speed_t last_code = B9600; // fall back |
456 | 0 | int index; |
457 | | |
458 | | // dumb linear search |
459 | 0 | for (index = 0; 0 != speed_code[index].speed; index++) { |
460 | 0 | if (speed < speed_code[index].speed) { |
461 | | // went past desired speed, use next slower valid speed |
462 | 0 | code = last_code; |
463 | 0 | break; |
464 | 0 | } |
465 | 0 | if (speed == speed_code[index].speed) { |
466 | 0 | code = speed_code[index].code; |
467 | 0 | break; |
468 | 0 | } |
469 | 0 | last_code = speed_code[index].code; |
470 | 0 | } |
471 | 0 | return code; |
472 | 0 | } |
473 | | |
474 | | // Convert speed code into speed |
475 | | static int code2speed(const speed_t code) |
476 | 0 | { |
477 | 0 | int speed = 9600; // fall back |
478 | 0 | int index; |
479 | | |
480 | | // dumb linear search |
481 | 0 | for (index = 0; 0 != speed_code[index].speed; index++) { |
482 | 0 | if (code == speed_code[index].code) { |
483 | 0 | speed = speed_code[index].speed; |
484 | 0 | break; |
485 | 0 | } |
486 | 0 | } |
487 | 0 | return speed; |
488 | 0 | } |
489 | | |
490 | | // return the speed for a device. B0, B9600, etc. |
491 | | int gpsd_get_speed(const struct gps_device_t *dev) |
492 | 0 | { |
493 | 0 | return code2speed(cfgetospeed(&dev->ttyset)); |
494 | 0 | } |
495 | | |
496 | | // return the old speed for a device. B0, B9600, etc. |
497 | | int gpsd_get_speed_old(const struct gps_device_t *dev) |
498 | 0 | { |
499 | 0 | return code2speed(cfgetospeed(&dev->ttyset_old)); |
500 | 0 | } |
501 | | |
502 | | // return the parity for a device. N, O or E. |
503 | | char gpsd_get_parity(const struct gps_device_t *dev) |
504 | 0 | { |
505 | 0 | char parity = 'N'; |
506 | |
|
507 | 0 | if ((PARENB | PARODD) == (dev->ttyset.c_cflag & (PARENB | PARODD))) { |
508 | 0 | parity = 'O'; |
509 | 0 | } else if (PARENB == (dev->ttyset.c_cflag & PARENB)) { |
510 | 0 | parity = 'E'; |
511 | 0 | } |
512 | 0 | return parity; |
513 | 0 | } |
514 | | |
515 | | // return the stop bits for a device. 1 or 2 |
516 | | int gpsd_get_stopbits(const struct gps_device_t *dev) |
517 | 0 | { |
518 | 0 | int stopbits = 1; |
519 | |
|
520 | 0 | if (CSTOPB == (dev->ttyset.c_cflag & CSTOPB)) { |
521 | | // xx2 |
522 | 0 | stopbits = 2; |
523 | 0 | } |
524 | 0 | return stopbits; |
525 | 0 | } |
526 | | |
527 | | /* |
528 | | * Serious black magic begins here. Getting this code wrong can cause |
529 | | * failures to lock to a correct speed, and not clean reproducible |
530 | | * failures but flukey hardware- and timing-dependent ones. So |
531 | | * be very sure you know what you're doing before hacking it, and |
532 | | * test thoroughly. |
533 | | * |
534 | | * The fundamental problem here is that serial devices take time |
535 | | * to settle into a new baud rate after tcsetattr() is issued. Until |
536 | | * they do so, input will be arbitrarily garbled. Normally this |
537 | | * is not a big problem, but in our hunt loop the garbling can trash |
538 | | * a long enough prefix of each sample to prevent detection of a |
539 | | * packet header. We could address the symptom by making the sample |
540 | | * size enough larger that subtracting the maximum length of garble |
541 | | * would still leave a sample longer than the maximum packet size. |
542 | | * But it's better (and more efficient) to address the disease. |
543 | | * |
544 | | * In theory, one might think that not even a tcflush() call would |
545 | | * be needed, with tcsetattr() delaying its return until the device |
546 | | * is in a good state. For simple devices like a 14550 UART that |
547 | | * have fixed response timings this may even work, if the driver |
548 | | * writer was smart enough to delay the return by the right number |
549 | | * of milliseconds after poking the device port(s). |
550 | | * |
551 | | * Problems may arise if the driver's timings are off. Or we may |
552 | | * be talking to a USB device like the pl2303 commonly used in GPS |
553 | | * mice; on these, the change will not happen immediately because |
554 | | * it has to be sent as a message to the external processor that |
555 | | * has to act upon it, and that processor may still have buffered |
556 | | * data in its own FIFO. In this case the expected delay may be |
557 | | * too large and too variable (depending on the details of how the |
558 | | * USB device is integrated with its symbiont hardware) to be put |
559 | | * in the driver. |
560 | | * |
561 | | * So, somehow, we have to introduce a delay after tcsatattr() |
562 | | * returns sufficient to allow *any* device to settle. On the other |
563 | | * hand, a really long delay will make gpsd device registration |
564 | | * unpleasantly laggy. |
565 | | * |
566 | | * The classic way to address this is with a tcflush(), counting |
567 | | * on it to clear the device FIFO. But that call may clear only the |
568 | | * kernel buffers, not the device's hardware FIFO, so it may not |
569 | | * be sufficient by itself. |
570 | | * |
571 | | * flush followed by a 200-millisecond delay followed by flush has |
572 | | * been found to work reliably on the pl2303. It is also known |
573 | | * from testing that a 100-millisec delay is too short, allowing |
574 | | * occasional failure to lock. |
575 | | */ |
576 | | static void gpsd_flush(struct gps_device_t * session) |
577 | 0 | { |
578 | 0 | struct timespec delay; |
579 | |
|
580 | 0 | if (0 != tcflush(session->gpsdata.gps_fd, TCIOFLUSH)) { |
581 | | // cast for 32-bit intptr_t |
582 | 0 | GPSD_LOG(LOG_ERROR, &session->context->errout, |
583 | 0 | "SER: gpsd_flush(%ld): %s(%d)\n", |
584 | 0 | (long)session->gpsdata.gps_fd, strerror(errno), errno); |
585 | 0 | } |
586 | | |
587 | | // wait 200,000 uSec |
588 | 0 | delay.tv_sec = 0; |
589 | 0 | delay.tv_nsec = 200000000L; |
590 | 0 | nanosleep(&delay, NULL); |
591 | 0 | if (0 != tcflush(session->gpsdata.gps_fd, TCIOFLUSH)) { |
592 | | // cast for 32-bit intptr_t |
593 | 0 | GPSD_LOG(LOG_ERROR, &session->context->errout, |
594 | 0 | "SER: gpsd_flush(%ld): %s(%d)\n", |
595 | 0 | (long)session->gpsdata.gps_fd, strerror(errno), errno); |
596 | 0 | } |
597 | 0 | } |
598 | | |
599 | | // Set device into raw mode |
600 | | bool gpsd_set_raw(struct gps_device_t * session) |
601 | 0 | { |
602 | | // on some OS cfmakeraw() returns an int, POSIX says it is void. |
603 | 0 | (void)cfmakeraw(&session->ttyset); |
604 | 0 | if (0 == tcsetattr(session->gpsdata.gps_fd, TCIOFLUSH, |
605 | 0 | &session->ttyset)) { |
606 | 0 | GPSD_LOG(LOG_ERROR, &session->context->errout, |
607 | 0 | "SER: error changing port attributes: %s(%d)\n", |
608 | 0 | strerror(errno), errno); |
609 | 0 | return false; |
610 | 0 | } |
611 | | |
612 | 0 | return true; |
613 | 0 | } |
614 | | |
615 | | /* Check if an fd is a tty |
616 | | * Return 1 if yes |
617 | | * 0 if no |
618 | | */ |
619 | | int gpsd_serial_isatty(const struct gps_device_t *session) |
620 | 0 | { |
621 | 0 | if (0 > session->gpsdata.gps_fd) { |
622 | | // PLACEHOLDING_FD, or UNALLOCATED_FD |
623 | | // no need for expensive iotcl() |
624 | 0 | return 0; |
625 | 0 | } |
626 | | // POSIX says isatty() does not have to set errno on error... |
627 | 0 | errno = 0; |
628 | 0 | if (0 < isatty(session->gpsdata.gps_fd)) { |
629 | | // is a tty |
630 | 0 | return 1; |
631 | 0 | } |
632 | 0 | if (ENOTTY == errno || |
633 | 0 | #if defined(ENXIO) |
634 | 0 | ENXIO == errno || // Some OSXes return this. Not POSIX. |
635 | 0 | #endif // defined(ENXIO) |
636 | 0 | #if defined(EADDRNOTAVAIL) |
637 | 0 | EADDRNOTAVAIL == errno || // Some BSDs return this. Not POSIX. |
638 | 0 | #endif // defined(EADDRNOTAVAIL) |
639 | 0 | #if defined(EOPNOTSUPP) |
640 | 0 | EOPNOTSUPP == errno || // Some BSDs/OSXes return this. Not POSIX. |
641 | 0 | #endif // defined(EOPNOTSUPP) |
642 | 0 | 0 == errno) { |
643 | | // is not a tty |
644 | 0 | return 0; |
645 | 0 | } |
646 | | |
647 | | // else warning, and assume not a tty./ cast for 32-bit intptr_t |
648 | 0 | GPSD_LOG(LOG_WARNING, &session->context->errout, |
649 | 0 | "SER: gpsd_serial_isatty(%ld) < 1: %s(%d)\n", |
650 | 0 | (long)session->gpsdata.gps_fd, |
651 | 0 | strerror(errno), errno); |
652 | 0 | return 0; |
653 | 0 | } |
654 | | |
655 | | // Set the port speed |
656 | | void gpsd_set_speed(struct gps_device_t *session, |
657 | | speed_t speed, char parity, unsigned int stopbits) |
658 | 0 | { |
659 | 0 | speed_t rate; |
660 | | |
661 | | // FIXME: just return if !isatty() ? |
662 | |
|
663 | 0 | if (0 < session->context->fixed_port_speed) { |
664 | 0 | speed = session->context->fixed_port_speed; |
665 | 0 | } |
666 | 0 | if ('\0' != session->context->fixed_port_framing[0]) { |
667 | | // Forced framing? |
668 | | // ignore length, stopbits=2 forces length 7. |
669 | 0 | parity = session->context->fixed_port_framing[1]; |
670 | 0 | stopbits = session->context->fixed_port_framing[2] - '0'; |
671 | 0 | } |
672 | 0 | if (!IN(1, stopbits, 2)) { |
673 | | // invalid stop bits, Assume 8x1 |
674 | 0 | stopbits = 1; |
675 | 0 | } |
676 | | |
677 | | /* |
678 | | * Yes, you can set speeds that aren't in the hunt loop. If you |
679 | | * do this, and you aren't on Linux where baud rate is preserved |
680 | | * across port closings, you've screwed yourself. Don't do that! |
681 | | * Setting the speed to B0 instructs the modem to "hang up". |
682 | | */ |
683 | 0 | rate = speed2code(speed); |
684 | | |
685 | | // backward-compatibility hack |
686 | 0 | switch (parity) { |
687 | 0 | case 'E': |
688 | 0 | FALLTHROUGH |
689 | 0 | case (char)2: |
690 | 0 | parity = 'E'; |
691 | 0 | break; |
692 | 0 | case 'O': |
693 | 0 | FALLTHROUGH |
694 | 0 | case (char)1: |
695 | 0 | parity = 'O'; |
696 | 0 | break; |
697 | 0 | case 'N': |
698 | 0 | FALLTHROUGH |
699 | 0 | case (char)0: |
700 | 0 | FALLTHROUGH |
701 | 0 | default: |
702 | 0 | parity = 'N'; // without this we might emit malformed JSON |
703 | 0 | break; |
704 | 0 | } |
705 | | |
706 | 0 | if (rate != cfgetispeed(&session->ttyset) || |
707 | 0 | parity != session->gpsdata.dev.parity || |
708 | 0 | stopbits != session->gpsdata.dev.stopbits) { |
709 | | |
710 | | /* |
711 | | * "Don't mess with this conditional! Speed zero is supposed to mean |
712 | | * to leave the port speed at whatever it currently is." |
713 | | * |
714 | | * The Linux man page says: |
715 | | * "Setting the speed to B0 instructs the modem to "hang up". |
716 | | * |
717 | | * We use B0 as an internal flag to leave the speed alone. |
718 | | * This leads |
719 | | * to excellent behavior on Linux, which preserves baudrate across |
720 | | * serial device closes - it means that if you've opened this |
721 | | * device before you typically don't have to hunt at all because |
722 | | * it's still at the same speed you left it - you'll typically |
723 | | * get packet lock within 1.5 seconds. Alas, the BSDs and OS X |
724 | | * aren't so nice. |
725 | | */ |
726 | 0 | if (B0 == rate) { |
727 | | // how does one get here? cast for 32-bit intptr_t |
728 | 0 | GPSD_LOG(LOG_IO, &session->context->errout, |
729 | 0 | "SER: fd %ld keeping old speed %d(%d)\n", |
730 | 0 | (long)session->gpsdata.gps_fd, |
731 | 0 | code2speed(cfgetispeed(&session->ttyset)), |
732 | 0 | (int) cfgetispeed(&session->ttyset)); |
733 | 0 | } else { |
734 | 0 | (void)cfsetispeed(&session->ttyset, rate); |
735 | 0 | (void)cfsetospeed(&session->ttyset, rate); |
736 | | // cast for 32-bit intptr_t |
737 | 0 | GPSD_LOG(LOG_IO, &session->context->errout, |
738 | 0 | "SER: fd %ld set speed %d(%d)\n", |
739 | 0 | (long)session->gpsdata.gps_fd, |
740 | 0 | code2speed(cfgetispeed(&session->ttyset)), (int) rate); |
741 | 0 | } |
742 | 0 | session->ttyset.c_iflag &= ~(PARMRK | INPCK); |
743 | 0 | session->ttyset.c_cflag &= ~(CSIZE | CSTOPB | PARENB | PARODD); |
744 | 0 | session->ttyset.c_cflag |= (stopbits == 2 ? CS7 | CSTOPB : CS8); |
745 | 0 | switch (parity) { |
746 | 0 | case 'E': |
747 | 0 | session->ttyset.c_iflag |= INPCK; |
748 | 0 | session->ttyset.c_cflag |= PARENB; |
749 | 0 | break; |
750 | 0 | case 'O': |
751 | 0 | session->ttyset.c_iflag |= INPCK; |
752 | 0 | session->ttyset.c_cflag |= PARENB | PARODD; |
753 | 0 | break; |
754 | 0 | } |
755 | 0 | if (0 != tcsetattr(session->gpsdata.gps_fd, TCSANOW, |
756 | 0 | &session->ttyset)) { |
757 | | /* strangely this fails on non-serial ports, but if |
758 | | * we do not try, we get other failures. |
759 | | * so ignore for now, as we always have, until it can |
760 | | * be nailed down. |
761 | | * cast for 32-bit intptr_t |
762 | | */ |
763 | 0 | GPSD_LOG(LOG_WARN, &session->context->errout, |
764 | 0 | "SER: fd %ld error setting port attributes: %s(%d), " |
765 | 0 | "sourcetype: %d\n", |
766 | 0 | (long)session->gpsdata.gps_fd, |
767 | 0 | strerror(errno), errno, session->sourcetype); |
768 | 0 | } |
769 | |
|
770 | 0 | gpsd_flush(session); |
771 | 0 | } |
772 | | // cast for 32-bit intptr_t |
773 | 0 | GPSD_LOG(LOG_INF, &session->context->errout, |
774 | 0 | "SER: fd %ld current speed %lu, %d%c%d\n", |
775 | 0 | (long)session->gpsdata.gps_fd, |
776 | 0 | (unsigned long)gpsd_get_speed(session), 9 - stopbits, parity, |
777 | 0 | stopbits); |
778 | |
|
779 | 0 | session->gpsdata.dev.baudrate = (unsigned int)speed; |
780 | 0 | session->gpsdata.dev.parity = parity; |
781 | 0 | session->gpsdata.dev.stopbits = stopbits; |
782 | | |
783 | | /* |
784 | | * The device might need a wakeup string before it will send data. |
785 | | * If we don't know the device type, ship it every driver's wakeup |
786 | | * in hopes it will respond. But not to USB or Bluetooth, because |
787 | | * shipping probe strings to unknown USB serial adaptors or |
788 | | * Bluetooth devices may spam devices that aren't GPSes at all and |
789 | | * could become confused. |
790 | | * For now we probe SOURCE_ACM... |
791 | | */ |
792 | 0 | if (!session->context->readonly && |
793 | 0 | SOURCE_USB != session->sourcetype && |
794 | 0 | SOURCE_BLUETOOTH != session->sourcetype) { |
795 | | |
796 | | // it is not read-only, not ttyACM, not BT |
797 | 0 | if (0 < gpsd_serial_isatty(session)) { |
798 | 0 | if (NULL == session->device_type) { |
799 | 0 | const struct gps_type_t **dp; |
800 | |
|
801 | 0 | for (dp = gpsd_drivers; *dp; dp++) { |
802 | 0 | if (NULL != (*dp)->event_hook) { |
803 | 0 | (*dp)->event_hook(session, EVENT_WAKEUP); |
804 | 0 | } |
805 | 0 | } |
806 | 0 | } else if (NULL != session->device_type->event_hook) { |
807 | 0 | session->device_type->event_hook(session, EVENT_WAKEUP); |
808 | 0 | } |
809 | 0 | } |
810 | 0 | } |
811 | 0 | packet_reset(&session->lexer); |
812 | 0 | clock_gettime(CLOCK_REALTIME, &session->ts_startCurrentBaud); |
813 | 0 | } |
814 | | |
815 | | /* open a device for access to its data |
816 | | * |
817 | | * return: the opened file descriptor |
818 | | * PLACEHOLDING_FD - for /dev/ppsX |
819 | | * UNALLOCATED_FD - for open failure |
820 | | */ |
821 | | int gpsd_serial_open(struct gps_device_t *session) |
822 | 0 | { |
823 | 0 | speed_t new_speed; |
824 | 0 | char new_parity; // E, N, O |
825 | 0 | unsigned int new_stop; |
826 | |
|
827 | 0 | mode_t mode = (mode_t) O_RDWR; |
828 | |
|
829 | 0 | session->sourcetype = gpsd_classify(session); |
830 | | |
831 | | // cast for 32-bit intptr_t |
832 | 0 | GPSD_LOG(LOG_PROG, &session->context->errout, |
833 | 0 | "SER: gpsd_serial_open(%s) sourcetype %s(%d) fd %ld\n", |
834 | 0 | session->gpsdata.dev.path, |
835 | 0 | val2str(session->sourcetype, sourcetypes), |
836 | 0 | session->sourcetype, |
837 | 0 | (long)session->gpsdata.gps_fd); |
838 | |
|
839 | 0 | session->servicetype = SERVICE_SENSOR; |
840 | |
|
841 | 0 | if (SOURCE_UNKNOWN == session->sourcetype) { |
842 | 0 | return UNALLOCATED_FD; |
843 | 0 | } |
844 | | |
845 | | // we may need to hold on to this slot without opening the device |
846 | 0 | if (SOURCE_PPS == session->sourcetype) { |
847 | 0 | (void)gpsd_switch_driver(session, "PPS"); |
848 | 0 | return PLACEHOLDING_FD; |
849 | 0 | } |
850 | | |
851 | 0 | if (session->context->readonly || |
852 | 0 | SOURCE_BLOCKDEV >= session->sourcetype) { |
853 | 0 | mode = (mode_t) O_RDONLY; |
854 | 0 | GPSD_LOG(LOG_INF, &session->context->errout, |
855 | 0 | "SER: opening read-only GPS data source type %d at '%s'\n", |
856 | 0 | (int)session->sourcetype, session->gpsdata.dev.path); |
857 | 0 | } else { |
858 | 0 | GPSD_LOG(LOG_INF, &session->context->errout, |
859 | 0 | "SER: opening GPS data source type %d at '%s'\n", |
860 | 0 | (int)session->sourcetype, session->gpsdata.dev.path); |
861 | 0 | } |
862 | | #ifdef ENABLE_BLUEZ |
863 | | if (0 == bachk(session->gpsdata.dev.path)) { |
864 | | struct sockaddr_rc addr = { 0, *BDADDR_ANY, 0}; |
865 | | |
866 | | errno = 0; |
867 | | session->gpsdata.gps_fd = socket(AF_BLUETOOTH, |
868 | | SOCK_STREAM, |
869 | | BTPROTO_RFCOMM); |
870 | | if (0 > session->gpsdata.gps_fd) { |
871 | | GPSD_LOG(LOG_ERROR, &session->context->errout, |
872 | | "SER: bluetooth socket() failed: %s(%d)\n", |
873 | | strerror(errno), errno); |
874 | | return UNALLOCATED_FD; |
875 | | } |
876 | | addr.rc_family = AF_BLUETOOTH; |
877 | | addr.rc_channel = (uint8_t) 1; |
878 | | (void) str2ba(session->gpsdata.dev.path, &addr.rc_bdaddr); |
879 | | if (-1 == connect(session->gpsdata.gps_fd, |
880 | | (struct sockaddr *) &addr, |
881 | | sizeof (addr))) { |
882 | | if (EINPROGRESS != errno && |
883 | | EAGAIN != errno) { |
884 | | (void)close(session->gpsdata.gps_fd); |
885 | | GPSD_LOG(LOG_ERROR, &session->context->errout, |
886 | | "SER: bluetooth socket connect failed: %s(%d)\n", |
887 | | strerror(errno), errno); |
888 | | return UNALLOCATED_FD; |
889 | | } |
890 | | GPSD_LOG(LOG_ERROR, &session->context->errout, |
891 | | "SER: bluetooth socket connect in progress or " |
892 | | "EAGAIN: %s(%d)\n", |
893 | | strerror(errno), errno); |
894 | | } |
895 | | (void)fcntl(session->gpsdata.gps_fd, F_SETFL, (int)mode); |
896 | | GPSD_LOG(LOG_PROG, &session->context->errout, |
897 | | "SER: bluez device open success: %s %s(%d)\n", |
898 | | session->gpsdata.dev.path, strerror(errno), errno); |
899 | | } else |
900 | | #endif // BLUEZ |
901 | 0 | { |
902 | | /* |
903 | | * We open with O_NONBLOCK because we want to not get hung if |
904 | | * the CLOCAL flag is off. Need to keep O_NONBLOCK so the main |
905 | | * loop does not clock on an unresponsive read() from a receiver. |
906 | | */ |
907 | 0 | errno = 0; |
908 | 0 | if (-1 == (session->gpsdata.gps_fd = |
909 | 0 | open(session->gpsdata.dev.path, |
910 | 0 | (int)(mode | O_NONBLOCK | O_NOCTTY)))) { |
911 | 0 | GPSD_LOG(LOG_ERROR, &session->context->errout, |
912 | 0 | "SER: device open of %s failed: %s(%d) - " |
913 | 0 | "retrying read-only\n", |
914 | 0 | session->gpsdata.dev.path, |
915 | 0 | strerror(errno), errno); |
916 | 0 | if (-1 == (session->gpsdata.gps_fd = |
917 | 0 | open(session->gpsdata.dev.path, |
918 | 0 | O_RDONLY | O_NONBLOCK | O_NOCTTY))) { |
919 | 0 | GPSD_LOG(LOG_ERROR, &session->context->errout, |
920 | 0 | "SER: read-only device open of %s failed: %s(%d)\n", |
921 | 0 | session->gpsdata.dev.path, |
922 | 0 | strerror(errno), errno); |
923 | 0 | return UNALLOCATED_FD; |
924 | 0 | } |
925 | | |
926 | | // cast for 32-bit intptr_t |
927 | 0 | GPSD_LOG(LOG_PROG, &session->context->errout, |
928 | 0 | "SER: file device open of %s succeeded fd %ld\n", |
929 | 0 | session->gpsdata.dev.path, |
930 | 0 | (long)session->gpsdata.gps_fd); |
931 | 0 | } |
932 | 0 | } |
933 | | |
934 | | /* |
935 | | * Ideally we want to exclusion-lock the device before doing any reads. |
936 | | * It would have been best to do this at open(2) time, but O_EXCL |
937 | | * doesn't work without O_CREAT. |
938 | | * |
939 | | * We have to make an exception for ptys, which are intentionally |
940 | | * opened by another process on the master side, otherwise we'll |
941 | | * break all our regression tests. |
942 | | * |
943 | | * We also exclude bluetooth device because the bluetooth daemon opens them. |
944 | | */ |
945 | 0 | if (!(SOURCE_PTY == session->sourcetype || |
946 | 0 | SOURCE_BLUETOOTH == session->sourcetype)) { |
947 | | // FIXME: OK to open PPS devices twice. |
948 | | // FIXME: Check for duplicates before opening device. |
949 | |
|
950 | 0 | #ifdef __linux__ |
951 | 0 | int cnt; |
952 | | /* |
953 | | * Don't touch devices already opened by another process. |
954 | | */ |
955 | 0 | cnt = fusercount(session); |
956 | 0 | if (1 < cnt) { |
957 | 0 | GPSD_LOG(LOG_ERROR, &session->context->errout, |
958 | 0 | "SER: %s already opened by another process!!!!!!!!!!!\n", |
959 | 0 | session->gpsdata.dev.path); |
960 | | /* caan't really continue, make it obvious to the user as |
961 | | * none understand what ERROR means. */ |
962 | 0 | exit(1); |
963 | | |
964 | | // (void)close(session->gpsdata.gps_fd); |
965 | | // session->gpsdata.gps_fd = UNALLOCATED_FD; |
966 | | // return UNALLOCATED_FD; |
967 | 0 | } |
968 | 0 | if (0 == cnt) { |
969 | 0 | GPSD_LOG(LOG_PROG, &session->context->errout, |
970 | 0 | "SER: fusercount(%s) failed to find own use.\n", |
971 | 0 | session->gpsdata.dev.path); |
972 | 0 | } |
973 | 0 | #endif // __linux__ |
974 | |
|
975 | 0 | #ifdef TIOCEXCL |
976 | | /* |
977 | | * Try to block other processes from using this device while we |
978 | | * have it open (later opens should return EBUSY). Won't work |
979 | | * against anything with root privileges, alas. |
980 | | */ |
981 | 0 | (void)ioctl(session->gpsdata.gps_fd, (unsigned long)TIOCEXCL); |
982 | 0 | #endif // TIOCEXCL |
983 | 0 | } |
984 | | |
985 | 0 | session->lexer.type = BAD_PACKET; |
986 | |
|
987 | 0 | if (0 >= gpsd_serial_isatty(session)) { |
988 | | // cast for 32-bit intptr_t |
989 | 0 | GPSD_LOG(LOG_IO, &session->context->errout, |
990 | 0 | "SER: gpsd_serial_open(%s) -> %ld, Not tty\n", |
991 | 0 | session->gpsdata.dev.path, (long)session->gpsdata.gps_fd); |
992 | | // (int) to pacify Codacy. |
993 | 0 | return (int)session->gpsdata.gps_fd; |
994 | 0 | } |
995 | | |
996 | | // Save original terminal parameters, why? |
997 | | // At least it tests we can read port parameters. |
998 | 0 | if (0 != tcgetattr(session->gpsdata.gps_fd, &session->ttyset_old)) { |
999 | | // Maybe still usable somehow? cast for 32-bit intptr_t |
1000 | 0 | GPSD_LOG(LOG_ERROR, &session->context->errout, |
1001 | 0 | "SER: gpsd_serial_open() tcgetattr(%ld) failed: %s(%d)\n", |
1002 | 0 | (long)session->gpsdata.gps_fd, |
1003 | 0 | strerror(errno), errno); |
1004 | 0 | return UNALLOCATED_FD; |
1005 | 0 | } |
1006 | 0 | session->ttyset = session->ttyset_old; |
1007 | |
|
1008 | 0 | if (0 < session->context->fixed_port_speed) { |
1009 | 0 | session->saved_baud = session->context->fixed_port_speed; |
1010 | | // cast for 32-bit intptr_t |
1011 | 0 | GPSD_LOG(LOG_PROG, &session->context->errout, |
1012 | 0 | "SER: fd %ld fixed speed %d\n", |
1013 | 0 | (long)session->gpsdata.gps_fd, session->saved_baud); |
1014 | 0 | } else if (0 < session->saved_baud) { |
1015 | | // cast for 32-bit intptr_t |
1016 | 0 | GPSD_LOG(LOG_PROG, &session->context->errout, |
1017 | 0 | "SER: fd %ld saved speed %d\n", |
1018 | 0 | (long)session->gpsdata.gps_fd, session->saved_baud); |
1019 | 0 | } |
1020 | |
|
1021 | 0 | if (0 < session->saved_baud) { |
1022 | | // FIXME! use gpsd_set_speed() |
1023 | 0 | (void)cfsetispeed(&session->ttyset, (speed_t)session->saved_baud); |
1024 | 0 | (void)cfsetospeed(&session->ttyset, (speed_t)session->saved_baud); |
1025 | 0 | if (0 == tcsetattr(session->gpsdata.gps_fd, TCSANOW, |
1026 | 0 | &session->ttyset)) { |
1027 | | // cast for 32-bit intptr_t |
1028 | 0 | GPSD_LOG(LOG_PROG, &session->context->errout, |
1029 | 0 | "SER: fd %ld restoring fixed/saved speed %d(%d)\n", |
1030 | 0 | (long)session->gpsdata.gps_fd, session->saved_baud, |
1031 | 0 | (int) cfgetispeed(&session->ttyset)); |
1032 | 0 | } else { |
1033 | | // cast for 32-bit intptr_t |
1034 | 0 | GPSD_LOG(LOG_ERROR, &session->context->errout, |
1035 | 0 | "SER: fd %ld Error setting port attributes: %s(%d)\n", |
1036 | 0 | (long)session->gpsdata.gps_fd, strerror(errno), errno); |
1037 | 0 | } |
1038 | 0 | gpsd_flush(session); |
1039 | 0 | } |
1040 | | |
1041 | | // twiddle the speed, parity, etc. but only on real serial ports |
1042 | 0 | memset(session->ttyset.c_cc, 0, sizeof(session->ttyset.c_cc)); |
1043 | | // session->ttyset.c_cc[VTIME] = 1; |
1044 | | /* |
1045 | | * Tip from Chris Kuethe: the FIDI chip used in the Trip-Nav |
1046 | | * 200 (and possibly other USB GPSes) gets completely hosed |
1047 | | * in the presence of flow control. Thus, turn off CRTSCTS. |
1048 | | * |
1049 | | * This is not ideal. Setting no parity here will mean extra |
1050 | | * initialization time for some devices, like certain Trimble |
1051 | | * boards, that want 7O2 or other non-8N1 settings. But starting |
1052 | | * the hunt loop at 8N1 will minimize the average sync time |
1053 | | * over all devices. |
1054 | | */ |
1055 | 0 | session->ttyset.c_cflag &= ~(PARENB | PARODD | CRTSCTS | CSTOPB); |
1056 | 0 | session->ttyset.c_cflag |= CREAD | CLOCAL; |
1057 | 0 | session->ttyset.c_iflag = session->ttyset.c_oflag = |
1058 | 0 | session->ttyset.c_lflag = (tcflag_t) 0; |
1059 | |
|
1060 | 0 | session->baudindex = 0; // FIXME: fixed speed |
1061 | 0 | if (0 < session->context->fixed_port_speed) { |
1062 | 0 | new_speed = session->context->fixed_port_speed; |
1063 | 0 | } else { |
1064 | 0 | new_speed = gpsd_get_speed_old(session); |
1065 | 0 | } |
1066 | 0 | if ('\0' == session->context->fixed_port_framing[0]) { |
1067 | | // FIXME! Try the parity, stop, as it is on startup first. |
1068 | 0 | new_parity = 'N'; |
1069 | 0 | new_stop = 1; |
1070 | 0 | } else { |
1071 | | // Forced framing |
1072 | | // ignore length, stopbits=2 forces length 7. |
1073 | 0 | new_parity = session->context->fixed_port_framing[1]; |
1074 | 0 | new_stop = session->context->fixed_port_framing[2] - '0'; |
1075 | 0 | } |
1076 | 0 | if (!IN(1, new_stop, 2)) { |
1077 | | // invalid stop bits, assume 1 |
1078 | 0 | new_stop = 1; |
1079 | 0 | } |
1080 | | // FIXME: setting speed twice?? |
1081 | 0 | gpsd_set_speed(session, new_speed, new_parity, new_stop); |
1082 | | |
1083 | | /* Used to turn off O_NONBLOCK here, but best not to block trying |
1084 | | * to read from an unresponsive receiver. */ |
1085 | | |
1086 | | // required so parity field won't be '\0' if saved speed matches |
1087 | 0 | if (SOURCE_BLOCKDEV >= session->sourcetype) { |
1088 | 0 | session->gpsdata.dev.parity = 'N'; |
1089 | 0 | session->gpsdata.dev.stopbits = 1; |
1090 | 0 | } |
1091 | | |
1092 | | // start the autobaud hunt clock. |
1093 | 0 | clock_gettime(CLOCK_REALTIME, &session->ts_startCurrentBaud); |
1094 | | // cast for 32-bit intptr_t |
1095 | 0 | GPSD_LOG(LOG_IO, &session->context->errout, |
1096 | 0 | "SER: open(%s) -> %ld in gpsd_serial_open()\n", |
1097 | 0 | session->gpsdata.dev.path, (long)session->gpsdata.gps_fd); |
1098 | 0 | return session->gpsdata.gps_fd; |
1099 | 0 | } |
1100 | | |
1101 | | ssize_t gpsd_serial_write(struct gps_device_t * session, |
1102 | | const char *buf, const size_t len) |
1103 | 0 | { |
1104 | 0 | ssize_t status; |
1105 | 0 | bool ok; |
1106 | 0 | char scratchbuf[MAX_PACKET_LENGTH*2+1]; |
1107 | |
|
1108 | 0 | if (NULL == session || |
1109 | 0 | NULL == session->context || |
1110 | 0 | 0 > session->gpsdata.gps_fd || |
1111 | 0 | session->context->readonly) { |
1112 | 0 | return 0; |
1113 | 0 | } |
1114 | | |
1115 | 0 | status = write(session->gpsdata.gps_fd, buf, len); |
1116 | 0 | ok = (status == (ssize_t) len); |
1117 | 0 | if (0 < gpsd_serial_isatty(session)) { |
1118 | | // do we really need to block on tcdrain? |
1119 | 0 | if (0 != tcdrain(session->gpsdata.gps_fd)) { |
1120 | | // cast for 32-bit intptr_t |
1121 | 0 | GPSD_LOG(LOG_ERROR, &session->context->errout, |
1122 | 0 | "SER: gpsd_serial_write(%ld) tcdrain() failed: %s(%d)\n", |
1123 | 0 | (long)session->gpsdata.gps_fd, |
1124 | 0 | strerror(errno), errno); |
1125 | 0 | } |
1126 | 0 | } |
1127 | |
|
1128 | 0 | GPSD_LOG(LOG_IO, &session->context->errout, |
1129 | 0 | "SER: => GPS: %s%s\n", |
1130 | 0 | gpsd_packetdump(scratchbuf, sizeof(scratchbuf), |
1131 | 0 | (const unsigned char *)buf, |
1132 | 0 | len), ok ? "" : " FAILED"); |
1133 | 0 | return status; |
1134 | 0 | } |
1135 | | |
1136 | | /* |
1137 | | * This constant controls how many characters the packet sniffer will spend |
1138 | | * looking for a packet leader before it gives up. It *must* be larger than |
1139 | | * MAX_PACKET_LENGTH or we risk never syncing up at all. Large values |
1140 | | * will produce annoying startup lag. |
1141 | | */ |
1142 | 0 | #define SNIFF_RETRIES (MAX_PACKET_LENGTH + 128) |
1143 | | |
1144 | | // advance to the next hunt setting |
1145 | | bool gpsd_next_hunt_setting(struct gps_device_t * session) |
1146 | 0 | { |
1147 | 0 | struct timespec ts_now, ts_diff; |
1148 | 0 | #ifdef TIOCGICOUNT |
1149 | | // serial input counters |
1150 | 0 | struct serial_icounter_struct icount; |
1151 | 0 | #endif // TIOCGICOUNT |
1152 | | |
1153 | | // don't waste time in the hunt loop if this is not actually a tty |
1154 | | // FIXME: Check for ttys like /dev/ttyACM that have no speed. |
1155 | 0 | if (0 >= gpsd_serial_isatty(session)) { |
1156 | | // This catches SERVICE_TCP, SERVICE_PIPE, SERVICE_GPSD, etc. |
1157 | 0 | return false; |
1158 | 0 | } |
1159 | | |
1160 | | /* ...or if it's nominally a tty but delivers only PPS and no data |
1161 | | * ...or if it's a PTY, which has no speed. */ |
1162 | 0 | if (SOURCE_PPS == session->sourcetype || |
1163 | 0 | SOURCE_PTY == session->sourcetype) { |
1164 | 0 | return false; |
1165 | 0 | } |
1166 | | |
1167 | | // ...or if the port speed is fixed. |
1168 | 0 | if (0 < session->context->fixed_port_speed) { |
1169 | | // this prevents framing hunt. |
1170 | 0 | return false; |
1171 | 0 | } |
1172 | | |
1173 | 0 | clock_gettime(CLOCK_REALTIME, &ts_now); |
1174 | 0 | TS_SUB(&ts_diff, &ts_now, &session->ts_startCurrentBaud); |
1175 | | |
1176 | | // cast for 32-bit intptr_t |
1177 | 0 | GPSD_LOG(LOG_IO, &session->context->errout, |
1178 | 0 | "SER: gpsd_next_hunt_setting(%ld) retries %lu diff %lld\n", |
1179 | 0 | (long)session->gpsdata.gps_fd, |
1180 | 0 | session->lexer.retry_counter, |
1181 | 0 | (long long)ts_diff.tv_sec); |
1182 | |
|
1183 | 0 | if (SNIFF_RETRIES <= session->lexer.retry_counter++ || |
1184 | 0 | 3 < ts_diff.tv_sec) { |
1185 | | // no lock after 3 seconds or SNIFF_RETRIES |
1186 | 0 | char new_parity; // E, N, O |
1187 | 0 | unsigned int new_stop; |
1188 | | // u-blox 9 can do 921600 |
1189 | | // Javad can ro 1.5 mbps |
1190 | | // every rate we're likely to see on a GNSS receiver |
1191 | 0 | static unsigned int rates[] = |
1192 | 0 | {0, 4800, 9600, 19200, 38400, 57600, 115200, 230400, |
1193 | 0 | 460800, 921600}; |
1194 | |
|
1195 | 0 | #ifdef TIOCGICOUNT |
1196 | | // check input counts |
1197 | 0 | if (LOG_INF > session->context->errout.debug) { |
1198 | | // do nothing |
1199 | 0 | } else if (-1 == ioctl(session->gpsdata.gps_fd, |
1200 | 0 | (unsigned long)TIOCGICOUNT, &icount)) { |
1201 | | // some tty-like devices do not implement TIOCGICOUNT |
1202 | 0 | if (ENOTTY != errno) { |
1203 | | // cast for 32-bit intptr_t |
1204 | 0 | GPSD_LOG(LOG_ERROR, &session->context->errout, |
1205 | 0 | "SER: ioctl(%ld, TIOCGICOUNT) failed: %s(%d)\n", |
1206 | 0 | (long)session->gpsdata.gps_fd, |
1207 | 0 | strerror(errno), errno); |
1208 | 0 | } |
1209 | 0 | } else { |
1210 | | // cast for 32-bit intptr_t |
1211 | 0 | GPSD_LOG(LOG_INF, &session->context->errout, |
1212 | 0 | "SER: ioctl(%ld, TIOCGICOUNT) rx %d tx %d frame %d " |
1213 | 0 | "overrun %d parity %d brk %d buf_overrun %d\n", |
1214 | 0 | (long)session->gpsdata.gps_fd, icount.rx, icount.tx, |
1215 | 0 | icount.frame, icount.overrun, icount.parity, icount.brk, |
1216 | 0 | icount.buf_overrun); |
1217 | 0 | } |
1218 | 0 | #endif // TIOCGICOUNT |
1219 | |
|
1220 | 0 | if (ROWS(rates) <= ++session->baudindex) { |
1221 | | |
1222 | | // start over, maybe with new stop bits. |
1223 | 0 | session->baudindex = 1; // skip reates[0]. which os B0. |
1224 | 0 | if ('\0' == session->context->fixed_port_framing[0]) { |
1225 | | // This toggles from 7N2 to 8N1, and back to 7N2 |
1226 | | // FIXME?? Also try 8E1 and 8O1 ?? For Trimble |
1227 | 0 | if (2 == session->gpsdata.dev.stopbits) { |
1228 | 0 | session->gpsdata.dev.stopbits = 1; // restart at 1 |
1229 | 0 | } else { |
1230 | 0 | session->gpsdata.dev.stopbits = 2; // increment to 2 |
1231 | 0 | } |
1232 | 0 | } |
1233 | 0 | } |
1234 | |
|
1235 | 0 | if ('\0' == session->context->fixed_port_framing[0]) { |
1236 | 0 | new_parity = session->gpsdata.dev.parity; |
1237 | 0 | new_stop = session->gpsdata.dev.stopbits; |
1238 | 0 | } else { |
1239 | | // fixed framing |
1240 | | // ignore length, stopbits=2 forces length 7. |
1241 | 0 | new_parity = session->context->fixed_port_framing[1]; |
1242 | 0 | new_stop = session->context->fixed_port_framing[2] - '0'; |
1243 | 0 | } |
1244 | |
|
1245 | 0 | gpsd_set_speed(session, rates[session->baudindex], new_parity, |
1246 | 0 | new_stop); |
1247 | 0 | session->lexer.retry_counter = 0; |
1248 | 0 | } |
1249 | 0 | return true; // keep hunting |
1250 | 0 | } |
1251 | | |
1252 | | // to be called when we want to register that we've synced with a device |
1253 | | void gpsd_assert_sync(struct gps_device_t *session) |
1254 | 0 | { |
1255 | | /* |
1256 | | * We've achieved first sync with the device. Remember the |
1257 | | * baudrate so we can try it first next time this device |
1258 | | * is opened. |
1259 | | */ |
1260 | 0 | if (0 >= session->saved_baud) { |
1261 | 0 | session->saved_baud = (int)cfgetispeed(&session->ttyset); |
1262 | 0 | } |
1263 | 0 | } |
1264 | | |
1265 | | /* Close an open sensor device |
1266 | | * could be serial, udp://, tcp://, etc. |
1267 | | * |
1268 | | * Return: void |
1269 | | */ |
1270 | | void gpsd_close(struct gps_device_t *session) |
1271 | 0 | { |
1272 | 0 | if (0 > session->gpsdata.gps_fd) { |
1273 | | // UNALLOCATED_FD (-1) or PLACEHOLDING_FD (-2). Nothing to do. |
1274 | 0 | return; |
1275 | 0 | } |
1276 | | |
1277 | 0 | if (0 < gpsd_serial_isatty(session)) { |
1278 | 0 | #ifdef TIOCNXCL |
1279 | | // This command resets the exclusive use of a terminal. |
1280 | 0 | if (-1 == ioctl(session->gpsdata.gps_fd, (unsigned long)TIOCNXCL)) { |
1281 | | // cast for 32-bit intptr_t |
1282 | 0 | GPSD_LOG(LOG_ERROR, &session->context->errout, |
1283 | 0 | "SER: ioctl(%ld, TIOCNXCL) failed: %s(%d)\n", |
1284 | 0 | (long)session->gpsdata.gps_fd, |
1285 | 0 | strerror(errno), errno); |
1286 | 0 | } |
1287 | 0 | #endif // TIOCNXCL |
1288 | 0 | if (!session->context->readonly) { |
1289 | | // Be sure all output is sent. |
1290 | 0 | if (0 != tcdrain(session->gpsdata.gps_fd)) { |
1291 | | // cast for 32-bit intptr_t |
1292 | 0 | GPSD_LOG(LOG_ERROR, &session->context->errout, |
1293 | 0 | "SER: gpsd_close(%ld) tcdrain() failed: %s(%d)\n", |
1294 | 0 | (long)session->gpsdata.gps_fd, |
1295 | 0 | strerror(errno), errno); |
1296 | 0 | } |
1297 | 0 | } |
1298 | | |
1299 | | // Save current terminal parameters. Why? |
1300 | 0 | if (0 != tcgetattr(session->gpsdata.gps_fd, &session->ttyset_old)) { |
1301 | 0 | GPSD_LOG(LOG_ERROR, &session->context->errout, |
1302 | 0 | "SER: gpsd_close() tcgetattr() failed: %s(%d)\n", |
1303 | 0 | strerror(errno), errno); |
1304 | 0 | } |
1305 | | |
1306 | | // force hangup on close on systems that don't do HUPCL properly |
1307 | | // is this still an issue? |
1308 | 0 | (void)cfsetispeed(&session->ttyset, (speed_t)B0); |
1309 | 0 | (void)cfsetospeed(&session->ttyset, (speed_t)B0); |
1310 | 0 | if (0 != tcsetattr(session->gpsdata.gps_fd, TCSANOW, |
1311 | 0 | &session->ttyset)) { |
1312 | 0 | GPSD_LOG(LOG_ERROR, &session->context->errout, |
1313 | 0 | "SER: tcsetattr(B0) failed: %s(%d)\n", |
1314 | 0 | strerror(errno), errno); |
1315 | 0 | } |
1316 | | |
1317 | | // this is the clean way to do it |
1318 | 0 | session->ttyset_old.c_cflag |= HUPCL; |
1319 | 0 | if (0 != tcsetattr(session->gpsdata.gps_fd, TCSANOW, |
1320 | 0 | &session->ttyset_old)) { |
1321 | 0 | GPSD_LOG(LOG_ERROR, &session->context->errout, |
1322 | 0 | "SER: tcsetattr(%d) failed: %s(%d)\n", |
1323 | 0 | session->gpsdata.dev.baudrate, strerror(errno), |
1324 | 0 | errno); |
1325 | 0 | } |
1326 | 0 | } else if (SOURCE_TCP == session->sourcetype) { |
1327 | | /* tcp:// sensor source |
1328 | | * close, but setup for reconnect |
1329 | | */ |
1330 | 0 | (void)close(session->gpsdata.gps_fd); |
1331 | | // Prepare for a retry |
1332 | 0 | session->gpsdata.gps_fd = PLACEHOLDING_FD; |
1333 | 0 | session->opentime = time(NULL); |
1334 | 0 | } |
1335 | | // cast for 32-bit intptr_t |
1336 | 0 | GPSD_LOG(LOG_IO, &session->context->errout, |
1337 | 0 | "SER: gpsd_close(%s), close(%ld)\n", |
1338 | 0 | session->gpsdata.dev.path, |
1339 | 0 | (long)session->gpsdata.gps_fd); |
1340 | 0 | if (!BAD_SOCKET(session->gpsdata.gps_fd)) { |
1341 | 0 | (void)close(session->gpsdata.gps_fd); |
1342 | 0 | session->gpsdata.gps_fd = UNALLOCATED_FD; |
1343 | 0 | } |
1344 | 0 | } |
1345 | | |
1346 | | // vim: set expandtab shiftwidth=4 |