1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2011-2012 Denis Shienkov <denis.shienkov@gmail.com> |
4 | ** Copyright (C) 2011 Sergey Belyashov <Sergey.Belyashov@gmail.com> |
5 | ** Copyright (C) 2012 Laszlo Papp <lpapp@kde.org> |
6 | ** Contact: https://www.qt.io/licensing/ |
7 | ** |
8 | ** This file is part of the QtSerialPort module of the Qt Toolkit. |
9 | ** |
10 | ** $QT_BEGIN_LICENSE:LGPL$ |
11 | ** Commercial License Usage |
12 | ** Licensees holding valid commercial Qt licenses may use this file in |
13 | ** accordance with the commercial license agreement provided with the |
14 | ** Software or, alternatively, in accordance with the terms contained in |
15 | ** a written agreement between you and The Qt Company. For licensing terms |
16 | ** and conditions see https://www.qt.io/terms-conditions. For further |
17 | ** information use the contact form at https://www.qt.io/contact-us. |
18 | ** |
19 | ** GNU Lesser General Public License Usage |
20 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
21 | ** General Public License version 3 as published by the Free Software |
22 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
23 | ** packaging of this file. Please review the following information to |
24 | ** ensure the GNU Lesser General Public License version 3 requirements |
25 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
26 | ** |
27 | ** GNU General Public License Usage |
28 | ** Alternatively, this file may be used under the terms of the GNU |
29 | ** General Public License version 2.0 or (at your option) the GNU General |
30 | ** Public license version 3 or any later version approved by the KDE Free |
31 | ** Qt Foundation. The licenses are as published by the Free Software |
32 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
33 | ** included in the packaging of this file. Please review the following |
34 | ** information to ensure the GNU General Public License requirements will |
35 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
36 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
37 | ** |
38 | ** $QT_END_LICENSE$ |
39 | ** |
40 | ****************************************************************************/ |
41 | |
42 | #include "qserialportinfo.h" |
43 | #include "qserialportinfo_p.h" |
44 | #include "qserialport_p.h" |
45 | |
46 | #include <QtCore/qlockfile.h> |
47 | #include <QtCore/qfile.h> |
48 | #include <QtCore/qdir.h> |
49 | #include <QtCore/qscopedpointer.h> |
50 | |
51 | #include <private/qcore_unix_p.h> |
52 | |
53 | #include <errno.h> |
54 | #include <sys/types.h> // kill |
55 | #include <signal.h> // kill |
56 | |
57 | #include "qtudev_p.h" |
58 | |
59 | QT_BEGIN_NAMESPACE |
60 | |
61 | static QStringList filteredDeviceFilePaths() |
62 | { |
63 | static const QStringList deviceFileNameFilterList = QStringList() |
64 | |
65 | #ifdef Q_OS_LINUX |
66 | << QStringLiteral("ttyS*" ) // Standard UART 8250 and etc. |
67 | << QStringLiteral("ttyO*" ) // OMAP UART 8250 and etc. |
68 | << QStringLiteral("ttyUSB*" ) // Usb/serial converters PL2303 and etc. |
69 | << QStringLiteral("ttyACM*" ) // CDC_ACM converters (i.e. Mobile Phones). |
70 | << QStringLiteral("ttyGS*" ) // Gadget serial device (i.e. Mobile Phones with gadget serial driver). |
71 | << QStringLiteral("ttyMI*" ) // MOXA pci/serial converters. |
72 | << QStringLiteral("ttymxc*" ) // Motorola IMX serial ports (i.e. Freescale i.MX). |
73 | << QStringLiteral("ttyAMA*" ) // AMBA serial device for embedded platform on ARM (i.e. Raspberry Pi). |
74 | << QStringLiteral("ttyTHS*" ) // Serial device for embedded platform on ARM (i.e. Tegra Jetson TK1). |
75 | << QStringLiteral("rfcomm*" ) // Bluetooth serial device. |
76 | << QStringLiteral("ircomm*" ) // IrDA serial device. |
77 | << QStringLiteral("tnt*" ); // Virtual tty0tty serial device. |
78 | #elif defined(Q_OS_FREEBSD) |
79 | << QStringLiteral("cu*" ); |
80 | #elif defined(Q_OS_QNX) |
81 | << QStringLiteral("ser*" ); |
82 | #else |
83 | ; |
84 | #endif |
85 | |
86 | QStringList result; |
87 | |
88 | QDir deviceDir(QStringLiteral("/dev" )); |
89 | if (deviceDir.exists()) { |
90 | deviceDir.setNameFilters(deviceFileNameFilterList); |
91 | deviceDir.setFilter(QDir::Files | QDir::System | QDir::NoSymLinks); |
92 | QStringList deviceFilePaths; |
93 | const auto deviceFileInfos = deviceDir.entryInfoList(); |
94 | for (const QFileInfo &deviceFileInfo : deviceFileInfos) { |
95 | const QString deviceAbsoluteFilePath = deviceFileInfo.absoluteFilePath(); |
96 | |
97 | #ifdef Q_OS_FREEBSD |
98 | // it is a quick workaround to skip the non-serial devices |
99 | if (deviceAbsoluteFilePath.endsWith(QLatin1String(".init" )) |
100 | || deviceAbsoluteFilePath.endsWith(QLatin1String(".lock" ))) { |
101 | continue; |
102 | } |
103 | #endif |
104 | |
105 | if (!deviceFilePaths.contains(str: deviceAbsoluteFilePath)) { |
106 | deviceFilePaths.append(t: deviceAbsoluteFilePath); |
107 | result.append(t: deviceAbsoluteFilePath); |
108 | } |
109 | } |
110 | } |
111 | |
112 | return result; |
113 | } |
114 | |
115 | QList<QSerialPortInfo> availablePortsByFiltersOfDevices(bool &ok) |
116 | { |
117 | QList<QSerialPortInfo> serialPortInfoList; |
118 | |
119 | const auto deviceFilePaths = filteredDeviceFilePaths(); |
120 | for (const QString &deviceFilePath : deviceFilePaths) { |
121 | QSerialPortInfoPrivate priv; |
122 | priv.device = deviceFilePath; |
123 | priv.portName = QSerialPortInfoPrivate::portNameFromSystemLocation(source: deviceFilePath); |
124 | serialPortInfoList.append(t: priv); |
125 | } |
126 | |
127 | ok = true; |
128 | return serialPortInfoList; |
129 | } |
130 | |
131 | static bool isSerial8250Driver(const QString &driverName) |
132 | { |
133 | return (driverName == QLatin1String("serial8250" )); |
134 | } |
135 | |
136 | static bool isValidSerial8250(const QString &systemLocation) |
137 | { |
138 | #ifdef Q_OS_LINUX |
139 | const mode_t flags = O_RDWR | O_NONBLOCK | O_NOCTTY; |
140 | const int fd = qt_safe_open(pathname: systemLocation.toLocal8Bit().constData(), flags); |
141 | if (fd != -1) { |
142 | struct serial_struct serinfo; |
143 | const int retval = ::ioctl(fd: fd, TIOCGSERIAL, &serinfo); |
144 | qt_safe_close(fd); |
145 | if (retval != -1 && serinfo.type != PORT_UNKNOWN) |
146 | return true; |
147 | } |
148 | #else |
149 | Q_UNUSED(systemLocation); |
150 | #endif |
151 | return false; |
152 | } |
153 | |
154 | static bool isRfcommDevice(const QString &portName) |
155 | { |
156 | if (!portName.startsWith(s: QLatin1String("rfcomm" ))) |
157 | return false; |
158 | |
159 | bool ok; |
160 | const int portNumber = portName.midRef(position: 6).toInt(ok: &ok); |
161 | if (!ok || (portNumber < 0) || (portNumber > 255)) |
162 | return false; |
163 | return true; |
164 | } |
165 | |
166 | // provided by the tty0tty driver |
167 | static bool isVirtualNullModemDevice(const QString &portName) |
168 | { |
169 | return portName.startsWith(s: QLatin1String("tnt" )); |
170 | } |
171 | |
172 | // provided by the g_serial driver |
173 | static bool isGadgetDevice(const QString &portName) |
174 | { |
175 | return portName.startsWith(s: QLatin1String("ttyGS" )); |
176 | } |
177 | |
178 | static QString ueventProperty(const QDir &targetDir, const QByteArray &pattern) |
179 | { |
180 | QFile f(QFileInfo(targetDir, QStringLiteral("uevent" )).absoluteFilePath()); |
181 | if (!f.open(flags: QIODevice::ReadOnly | QIODevice::Text)) |
182 | return QString(); |
183 | |
184 | const QByteArray content = f.readAll(); |
185 | |
186 | const int firstbound = content.indexOf(a: pattern); |
187 | if (firstbound == -1) |
188 | return QString(); |
189 | |
190 | const int lastbound = content.indexOf(c: '\n', from: firstbound); |
191 | return QString::fromLatin1( |
192 | str: content.mid(index: firstbound + pattern.size(), |
193 | len: lastbound - firstbound - pattern.size())) |
194 | .simplified(); |
195 | } |
196 | |
197 | static QString deviceName(const QDir &targetDir) |
198 | { |
199 | return ueventProperty(targetDir, pattern: "DEVNAME=" ); |
200 | } |
201 | |
202 | static QString deviceDriver(const QDir &targetDir) |
203 | { |
204 | const QDir deviceDir(targetDir.absolutePath() + QLatin1String("/device" )); |
205 | return ueventProperty(targetDir: deviceDir, pattern: "DRIVER=" ); |
206 | } |
207 | |
208 | static QString deviceProperty(const QString &targetFilePath) |
209 | { |
210 | QFile f(targetFilePath); |
211 | if (!f.open(flags: QIODevice::ReadOnly | QIODevice::Text)) |
212 | return QString(); |
213 | return QString::fromLatin1(str: f.readAll()).simplified(); |
214 | } |
215 | |
216 | static QString deviceDescription(const QDir &targetDir) |
217 | { |
218 | return deviceProperty(targetFilePath: QFileInfo(targetDir, QStringLiteral("product" )).absoluteFilePath()); |
219 | } |
220 | |
221 | static QString deviceManufacturer(const QDir &targetDir) |
222 | { |
223 | return deviceProperty(targetFilePath: QFileInfo(targetDir, QStringLiteral("manufacturer" )).absoluteFilePath()); |
224 | } |
225 | |
226 | static quint16 deviceProductIdentifier(const QDir &targetDir, bool &hasIdentifier) |
227 | { |
228 | QString result = deviceProperty(targetFilePath: QFileInfo(targetDir, QStringLiteral("idProduct" )).absoluteFilePath()); |
229 | if (result.isEmpty()) |
230 | result = deviceProperty(targetFilePath: QFileInfo(targetDir, QStringLiteral("device" )).absoluteFilePath()); |
231 | return result.toInt(ok: &hasIdentifier, base: 16); |
232 | } |
233 | |
234 | static quint16 deviceVendorIdentifier(const QDir &targetDir, bool &hasIdentifier) |
235 | { |
236 | QString result = deviceProperty(targetFilePath: QFileInfo(targetDir, QStringLiteral("idVendor" )).absoluteFilePath()); |
237 | if (result.isEmpty()) |
238 | result = deviceProperty(targetFilePath: QFileInfo(targetDir, QStringLiteral("vendor" )).absoluteFilePath()); |
239 | return result.toInt(ok: &hasIdentifier, base: 16); |
240 | } |
241 | |
242 | static QString deviceSerialNumber(const QDir &targetDir) |
243 | { |
244 | return deviceProperty(targetFilePath: QFileInfo(targetDir, QStringLiteral("serial" )).absoluteFilePath()); |
245 | } |
246 | |
247 | QList<QSerialPortInfo> availablePortsBySysfs(bool &ok) |
248 | { |
249 | QDir ttySysClassDir(QStringLiteral("/sys/class/tty" )); |
250 | |
251 | if (!(ttySysClassDir.exists() && ttySysClassDir.isReadable())) { |
252 | ok = false; |
253 | return QList<QSerialPortInfo>(); |
254 | } |
255 | |
256 | QList<QSerialPortInfo> serialPortInfoList; |
257 | ttySysClassDir.setFilter(QDir::Dirs | QDir::NoDotAndDotDot); |
258 | const auto fileInfos = ttySysClassDir.entryInfoList(); |
259 | for (const QFileInfo &fileInfo : fileInfos) { |
260 | if (!fileInfo.isSymLink()) |
261 | continue; |
262 | |
263 | QDir targetDir(fileInfo.symLinkTarget()); |
264 | |
265 | QSerialPortInfoPrivate priv; |
266 | |
267 | priv.portName = deviceName(targetDir); |
268 | if (priv.portName.isEmpty()) |
269 | continue; |
270 | |
271 | const QString driverName = deviceDriver(targetDir); |
272 | if (driverName.isEmpty()) { |
273 | if (!isRfcommDevice(portName: priv.portName) |
274 | && !isVirtualNullModemDevice(portName: priv.portName) |
275 | && !isGadgetDevice(portName: priv.portName)) { |
276 | continue; |
277 | } |
278 | } |
279 | |
280 | priv.device = QSerialPortInfoPrivate::portNameToSystemLocation(source: priv.portName); |
281 | if (isSerial8250Driver(driverName) && !isValidSerial8250(systemLocation: priv.device)) |
282 | continue; |
283 | |
284 | do { |
285 | if (priv.description.isEmpty()) |
286 | priv.description = deviceDescription(targetDir); |
287 | |
288 | if (priv.manufacturer.isEmpty()) |
289 | priv.manufacturer = deviceManufacturer(targetDir); |
290 | |
291 | if (priv.serialNumber.isEmpty()) |
292 | priv.serialNumber = deviceSerialNumber(targetDir); |
293 | |
294 | if (!priv.hasVendorIdentifier) |
295 | priv.vendorIdentifier = deviceVendorIdentifier(targetDir, hasIdentifier&: priv.hasVendorIdentifier); |
296 | |
297 | if (!priv.hasProductIdentifier) |
298 | priv.productIdentifier = deviceProductIdentifier(targetDir, hasIdentifier&: priv.hasProductIdentifier); |
299 | |
300 | if (!priv.description.isEmpty() |
301 | || !priv.manufacturer.isEmpty() |
302 | || !priv.serialNumber.isEmpty() |
303 | || priv.hasVendorIdentifier |
304 | || priv.hasProductIdentifier) { |
305 | break; |
306 | } |
307 | } while (targetDir.cdUp()); |
308 | |
309 | serialPortInfoList.append(t: priv); |
310 | } |
311 | |
312 | ok = true; |
313 | return serialPortInfoList; |
314 | } |
315 | |
316 | struct ScopedPointerUdevDeleter |
317 | { |
318 | static inline void cleanup(struct ::udev *pointer) |
319 | { |
320 | ::udev_unref(udev: pointer); |
321 | } |
322 | }; |
323 | |
324 | struct ScopedPointerUdevEnumeratorDeleter |
325 | { |
326 | static inline void cleanup(struct ::udev_enumerate *pointer) |
327 | { |
328 | ::udev_enumerate_unref(udev_enumerate: pointer); |
329 | } |
330 | }; |
331 | |
332 | struct ScopedPointerUdevDeviceDeleter |
333 | { |
334 | static inline void cleanup(struct ::udev_device *pointer) |
335 | { |
336 | ::udev_device_unref(udev_device: pointer); |
337 | } |
338 | }; |
339 | |
340 | #ifndef LINK_LIBUDEV |
341 | Q_GLOBAL_STATIC(QLibrary, udevLibrary) |
342 | #endif |
343 | |
344 | static QString deviceProperty(struct ::udev_device *dev, const char *name) |
345 | { |
346 | return QString::fromLatin1(str: ::udev_device_get_property_value(udev_device: dev, key: name)); |
347 | } |
348 | |
349 | static QString deviceDriver(struct ::udev_device *dev) |
350 | { |
351 | return QString::fromLatin1(str: ::udev_device_get_driver(udev_device: dev)); |
352 | } |
353 | |
354 | static QString deviceDescription(struct ::udev_device *dev) |
355 | { |
356 | return deviceProperty(dev, name: "ID_MODEL" ).replace(before: QLatin1Char('_'), after: QLatin1Char(' ')); |
357 | } |
358 | |
359 | static QString deviceManufacturer(struct ::udev_device *dev) |
360 | { |
361 | return deviceProperty(dev, name: "ID_VENDOR" ).replace(before: QLatin1Char('_'), after: QLatin1Char(' ')); |
362 | } |
363 | |
364 | static quint16 deviceProductIdentifier(struct ::udev_device *dev, bool &hasIdentifier) |
365 | { |
366 | return deviceProperty(dev, name: "ID_MODEL_ID" ).toInt(ok: &hasIdentifier, base: 16); |
367 | } |
368 | |
369 | static quint16 deviceVendorIdentifier(struct ::udev_device *dev, bool &hasIdentifier) |
370 | { |
371 | return deviceProperty(dev, name: "ID_VENDOR_ID" ).toInt(ok: &hasIdentifier, base: 16); |
372 | } |
373 | |
374 | static QString deviceSerialNumber(struct ::udev_device *dev) |
375 | { |
376 | return deviceProperty(dev,name: "ID_SERIAL_SHORT" ); |
377 | } |
378 | |
379 | static QString deviceName(struct ::udev_device *dev) |
380 | { |
381 | return QString::fromLatin1(str: ::udev_device_get_sysname(udev_device: dev)); |
382 | } |
383 | |
384 | static QString deviceLocation(struct ::udev_device *dev) |
385 | { |
386 | return QString::fromLatin1(str: ::udev_device_get_devnode(udev_device: dev)); |
387 | } |
388 | |
389 | QList<QSerialPortInfo> availablePortsByUdev(bool &ok) |
390 | { |
391 | ok = false; |
392 | |
393 | #ifndef LINK_LIBUDEV |
394 | static bool symbolsResolved = resolveSymbols(udevLibrary()); |
395 | if (!symbolsResolved) |
396 | return QList<QSerialPortInfo>(); |
397 | #endif |
398 | |
399 | QScopedPointer<struct ::udev, ScopedPointerUdevDeleter> udev(::udev_new()); |
400 | |
401 | if (!udev) |
402 | return QList<QSerialPortInfo>(); |
403 | |
404 | QScopedPointer<udev_enumerate, ScopedPointerUdevEnumeratorDeleter> |
405 | enumerate(::udev_enumerate_new(udev: udev.data())); |
406 | |
407 | if (!enumerate) |
408 | return QList<QSerialPortInfo>(); |
409 | |
410 | ::udev_enumerate_add_match_subsystem(udev_enumerate: enumerate.data(), subsystem: "tty" ); |
411 | ::udev_enumerate_scan_devices(udev_enumerate: enumerate.data()); |
412 | |
413 | udev_list_entry *devices = ::udev_enumerate_get_list_entry(udev_enumerate: enumerate.data()); |
414 | |
415 | QList<QSerialPortInfo> serialPortInfoList; |
416 | udev_list_entry *dev_list_entry; |
417 | udev_list_entry_foreach(dev_list_entry, devices) { |
418 | |
419 | ok = true; |
420 | |
421 | QScopedPointer<udev_device, ScopedPointerUdevDeviceDeleter> |
422 | dev(::udev_device_new_from_syspath( |
423 | udev: udev.data(), syspath: ::udev_list_entry_get_name(list_entry: dev_list_entry))); |
424 | |
425 | if (!dev) |
426 | return serialPortInfoList; |
427 | |
428 | QSerialPortInfoPrivate priv; |
429 | |
430 | priv.device = deviceLocation(dev: dev.data()); |
431 | priv.portName = deviceName(dev: dev.data()); |
432 | |
433 | udev_device *parentdev = ::udev_device_get_parent(udev_device: dev.data()); |
434 | |
435 | if (parentdev) { |
436 | const QString driverName = deviceDriver(dev: parentdev); |
437 | if (isSerial8250Driver(driverName) && !isValidSerial8250(systemLocation: priv.device)) |
438 | continue; |
439 | priv.description = deviceDescription(dev: dev.data()); |
440 | priv.manufacturer = deviceManufacturer(dev: dev.data()); |
441 | priv.serialNumber = deviceSerialNumber(dev: dev.data()); |
442 | priv.vendorIdentifier = deviceVendorIdentifier(dev: dev.data(), hasIdentifier&: priv.hasVendorIdentifier); |
443 | priv.productIdentifier = deviceProductIdentifier(dev: dev.data(), hasIdentifier&: priv.hasProductIdentifier); |
444 | } else { |
445 | if (!isRfcommDevice(portName: priv.portName) |
446 | && !isVirtualNullModemDevice(portName: priv.portName) |
447 | && !isGadgetDevice(portName: priv.portName)) { |
448 | continue; |
449 | } |
450 | } |
451 | |
452 | serialPortInfoList.append(t: priv); |
453 | } |
454 | |
455 | return serialPortInfoList; |
456 | } |
457 | |
458 | QList<QSerialPortInfo> QSerialPortInfo::availablePorts() |
459 | { |
460 | bool ok; |
461 | |
462 | QList<QSerialPortInfo> serialPortInfoList = availablePortsByUdev(ok); |
463 | |
464 | #ifdef Q_OS_LINUX |
465 | if (!ok) |
466 | serialPortInfoList = availablePortsBySysfs(ok); |
467 | #endif |
468 | |
469 | if (!ok) |
470 | serialPortInfoList = availablePortsByFiltersOfDevices(ok); |
471 | |
472 | return serialPortInfoList; |
473 | } |
474 | |
475 | #if QT_DEPRECATED_SINCE(5, 6) |
476 | bool QSerialPortInfo::isBusy() const |
477 | { |
478 | QString lockFilePath = serialPortLockFilePath(portName: portName()); |
479 | if (lockFilePath.isEmpty()) |
480 | return false; |
481 | |
482 | QFile reader(lockFilePath); |
483 | if (!reader.open(flags: QIODevice::ReadOnly)) |
484 | return false; |
485 | |
486 | QByteArray pidLine = reader.readLine(); |
487 | pidLine.chop(n: 1); |
488 | if (pidLine.isEmpty()) |
489 | return false; |
490 | |
491 | qint64 pid = pidLine.toLongLong(); |
492 | |
493 | if (pid && (::kill(pid: pid, sig: 0) == -1) && (errno == ESRCH)) |
494 | return false; // PID doesn't exist anymore |
495 | |
496 | return true; |
497 | } |
498 | #endif // QT_DEPRECATED_SINCE(5, 6) |
499 | |
500 | #if QT_DEPRECATED_SINCE(5, 2) |
501 | bool QSerialPortInfo::isValid() const |
502 | { |
503 | QFile f(systemLocation()); |
504 | return f.exists(); |
505 | } |
506 | #endif // QT_DEPRECATED_SINCE(5, 2) |
507 | |
508 | QString QSerialPortInfoPrivate::portNameToSystemLocation(const QString &source) |
509 | { |
510 | return (source.startsWith(c: QLatin1Char('/')) |
511 | || source.startsWith(s: QLatin1String("./" )) |
512 | || source.startsWith(s: QLatin1String("../" ))) |
513 | ? source : (QLatin1String("/dev/" ) + source); |
514 | } |
515 | |
516 | QString QSerialPortInfoPrivate::portNameFromSystemLocation(const QString &source) |
517 | { |
518 | return source.startsWith(s: QLatin1String("/dev/" )) |
519 | ? source.mid(position: 5) : source; |
520 | } |
521 | |
522 | QT_END_NAMESPACE |
523 | |