1// Copyright (C) 2011-2012 Denis Shienkov <denis.shienkov@gmail.com>
2// Copyright (C) 2011 Sergey Belyashov <Sergey.Belyashov@gmail.com>
3// Copyright (C) 2012 Laszlo Papp <lpapp@kde.org>
4// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
5
6#include "qserialportinfo.h"
7#include "qserialportinfo_p.h"
8#include "qserialport_p.h"
9
10#include <QtCore/qlockfile.h>
11#include <QtCore/qfile.h>
12#include <QtCore/qdir.h>
13#include <QtCore/qtenvironmentvariables.h>
14
15#include <private/qcore_unix_p.h>
16
17#include <memory>
18
19#include <errno.h>
20#include <sys/types.h> // kill
21#include <signal.h> // kill
22
23#include "qtudev_p.h"
24
25QT_BEGIN_NAMESPACE
26
27constexpr char SkipUdevEnvVarName[] = "QT_SERIALPORT_SKIP_UDEV_LOOKUP";
28
29static QStringList filteredDeviceFilePaths()
30{
31 static const QStringList deviceFileNameFilterList = QStringList()
32
33#ifdef Q_OS_LINUX
34 << QStringLiteral("ttyS*") // Standard UART 8250 and etc.
35 << QStringLiteral("ttyO*") // OMAP UART 8250 and etc.
36 << QStringLiteral("ttyUSB*") // Usb/serial converters PL2303 and etc.
37 << QStringLiteral("ttyACM*") // CDC_ACM converters (i.e. Mobile Phones).
38 << QStringLiteral("ttyGS*") // Gadget serial device (i.e. Mobile Phones with gadget serial driver).
39 << QStringLiteral("ttyMI*") // MOXA pci/serial converters.
40 << QStringLiteral("ttymxc*") // Motorola IMX serial ports (i.e. Freescale i.MX).
41 << QStringLiteral("ttyAMA*") // AMBA serial device for embedded platform on ARM (i.e. Raspberry Pi).
42 << QStringLiteral("ttyTHS*") // Serial device for embedded platform on ARM (i.e. Tegra Jetson TK1).
43 << QStringLiteral("rfcomm*") // Bluetooth serial device.
44 << QStringLiteral("ircomm*") // IrDA serial device.
45 << QStringLiteral("tnt*"); // Virtual tty0tty serial device.
46#elif defined(Q_OS_FREEBSD)
47 << QStringLiteral("cu*");
48#elif defined(Q_OS_QNX)
49 << QStringLiteral("ser*");
50#else
51 ;
52#endif
53
54 QStringList result;
55
56 QDir deviceDir(QStringLiteral("/dev"));
57 if (deviceDir.exists()) {
58 deviceDir.setNameFilters(deviceFileNameFilterList);
59 deviceDir.setFilter(QDir::Files | QDir::System | QDir::NoSymLinks);
60 QStringList deviceFilePaths;
61 const auto deviceFileInfos = deviceDir.entryInfoList();
62 for (const QFileInfo &deviceFileInfo : deviceFileInfos) {
63 const QString deviceAbsoluteFilePath = deviceFileInfo.absoluteFilePath();
64
65#ifdef Q_OS_FREEBSD
66 // it is a quick workaround to skip the non-serial devices
67 if (deviceAbsoluteFilePath.endsWith(QLatin1String(".init"))
68 || deviceAbsoluteFilePath.endsWith(QLatin1String(".lock"))) {
69 continue;
70 }
71#endif
72
73 if (!deviceFilePaths.contains(str: deviceAbsoluteFilePath)) {
74 deviceFilePaths.append(t: deviceAbsoluteFilePath);
75 result.append(t: deviceAbsoluteFilePath);
76 }
77 }
78 }
79
80 return result;
81}
82
83QList<QSerialPortInfo> availablePortsByFiltersOfDevices(bool &ok)
84{
85 QList<QSerialPortInfo> serialPortInfoList;
86
87 const auto deviceFilePaths = filteredDeviceFilePaths();
88 for (const QString &deviceFilePath : deviceFilePaths) {
89 QSerialPortInfoPrivate priv;
90 priv.device = deviceFilePath;
91 priv.portName = QSerialPortInfoPrivate::portNameFromSystemLocation(source: deviceFilePath);
92 serialPortInfoList.append(t: priv);
93 }
94
95 ok = true;
96 return serialPortInfoList;
97}
98
99static bool isSerial8250Driver(const QString &driverName)
100{
101 return (driverName == QLatin1String("serial8250"));
102}
103
104static bool isValidSerial8250(const QString &systemLocation)
105{
106#ifdef Q_OS_LINUX
107 const mode_t flags = O_RDWR | O_NONBLOCK | O_NOCTTY;
108 const int fd = qt_safe_open(pathname: systemLocation.toLocal8Bit().constData(), flags);
109 if (fd != -1) {
110 struct serial_struct serinfo;
111 const int retval = ::ioctl(fd: fd, TIOCGSERIAL, &serinfo);
112 qt_safe_close(fd);
113 if (retval != -1 && serinfo.type != PORT_UNKNOWN)
114 return true;
115 }
116#else
117 Q_UNUSED(systemLocation);
118#endif
119 return false;
120}
121
122static bool isRfcommDevice(QStringView portName)
123{
124 if (!portName.startsWith(s: QLatin1String("rfcomm")))
125 return false;
126
127 bool ok;
128 const int portNumber = portName.mid(pos: 6).toInt(ok: &ok);
129 if (!ok || (portNumber < 0) || (portNumber > 255))
130 return false;
131 return true;
132}
133
134// provided by the tty0tty driver
135static bool isVirtualNullModemDevice(const QString &portName)
136{
137 return portName.startsWith(s: QLatin1String("tnt"));
138}
139
140// provided by the g_serial driver
141static bool isGadgetDevice(const QString &portName)
142{
143 return portName.startsWith(s: QLatin1String("ttyGS"));
144}
145
146static QString ueventProperty(const QDir &targetDir, const QByteArray &pattern)
147{
148 QFile f(QFileInfo(targetDir, QStringLiteral("uevent")).absoluteFilePath());
149 if (!f.open(flags: QIODevice::ReadOnly | QIODevice::Text))
150 return QString();
151
152 const QByteArray content = f.readAll();
153
154 const qsizetype firstbound = content.indexOf(bv: pattern);
155 if (firstbound == -1)
156 return QString();
157
158 qsizetype lastbound = content.indexOf(ch: '\n', from: firstbound);
159 if (lastbound == -1)
160 lastbound = content.size();
161 return QString::fromLatin1(
162 ba: content.mid(index: firstbound + pattern.size(),
163 len: lastbound - firstbound - pattern.size()))
164 .simplified();
165}
166
167static QString deviceName(const QDir &targetDir)
168{
169 return ueventProperty(targetDir, pattern: "DEVNAME=");
170}
171
172static QString deviceDriver(const QDir &targetDir)
173{
174 const QDir deviceDir(targetDir.absolutePath() + QLatin1String("/device"));
175 return ueventProperty(targetDir: deviceDir, pattern: "DRIVER=");
176}
177
178static QString deviceProperty(const QString &targetFilePath)
179{
180 QFile f(targetFilePath);
181 if (!f.open(flags: QIODevice::ReadOnly | QIODevice::Text))
182 return QString();
183 return QString::fromLatin1(ba: f.readAll()).simplified();
184}
185
186static QString deviceDescription(const QDir &targetDir)
187{
188 return deviceProperty(targetFilePath: QFileInfo(targetDir, QStringLiteral("product")).absoluteFilePath());
189}
190
191static QString deviceManufacturer(const QDir &targetDir)
192{
193 return deviceProperty(targetFilePath: QFileInfo(targetDir, QStringLiteral("manufacturer")).absoluteFilePath());
194}
195
196static quint16 deviceProductIdentifier(const QDir &targetDir, bool &hasIdentifier)
197{
198 QString result = deviceProperty(targetFilePath: QFileInfo(targetDir, QStringLiteral("idProduct")).absoluteFilePath());
199 if (result.isEmpty())
200 result = deviceProperty(targetFilePath: QFileInfo(targetDir, QStringLiteral("device")).absoluteFilePath());
201 return result.toInt(ok: &hasIdentifier, base: 16);
202}
203
204static quint16 deviceVendorIdentifier(const QDir &targetDir, bool &hasIdentifier)
205{
206 QString result = deviceProperty(targetFilePath: QFileInfo(targetDir, QStringLiteral("idVendor")).absoluteFilePath());
207 if (result.isEmpty())
208 result = deviceProperty(targetFilePath: QFileInfo(targetDir, QStringLiteral("vendor")).absoluteFilePath());
209 return result.toInt(ok: &hasIdentifier, base: 16);
210}
211
212static QString deviceSerialNumber(const QDir &targetDir)
213{
214 return deviceProperty(targetFilePath: QFileInfo(targetDir, QStringLiteral("serial")).absoluteFilePath());
215}
216
217QList<QSerialPortInfo> availablePortsBySysfs(bool &ok)
218{
219 QDir ttySysClassDir(QStringLiteral("/sys/class/tty"));
220
221 if (!(ttySysClassDir.exists() && ttySysClassDir.isReadable())) {
222 ok = false;
223 return QList<QSerialPortInfo>();
224 }
225
226 QList<QSerialPortInfo> serialPortInfoList;
227 ttySysClassDir.setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
228 const auto fileInfos = ttySysClassDir.entryInfoList();
229 for (const QFileInfo &fileInfo : fileInfos) {
230 if (!fileInfo.isSymLink())
231 continue;
232
233 QDir targetDir(fileInfo.symLinkTarget());
234
235 QSerialPortInfoPrivate priv;
236
237 priv.portName = deviceName(targetDir);
238 if (priv.portName.isEmpty())
239 continue;
240
241 const QString driverName = deviceDriver(targetDir);
242 if (driverName.isEmpty()) {
243 if (!isRfcommDevice(portName: priv.portName)
244 && !isVirtualNullModemDevice(portName: priv.portName)
245 && !isGadgetDevice(portName: priv.portName)) {
246 continue;
247 }
248 }
249
250 priv.device = QSerialPortInfoPrivate::portNameToSystemLocation(source: priv.portName);
251 if (isSerial8250Driver(driverName) && !isValidSerial8250(systemLocation: priv.device))
252 continue;
253
254 do {
255 if (priv.description.isEmpty())
256 priv.description = deviceDescription(targetDir);
257
258 if (priv.manufacturer.isEmpty())
259 priv.manufacturer = deviceManufacturer(targetDir);
260
261 if (priv.serialNumber.isEmpty())
262 priv.serialNumber = deviceSerialNumber(targetDir);
263
264 if (!priv.hasVendorIdentifier)
265 priv.vendorIdentifier = deviceVendorIdentifier(targetDir, hasIdentifier&: priv.hasVendorIdentifier);
266
267 if (!priv.hasProductIdentifier)
268 priv.productIdentifier = deviceProductIdentifier(targetDir, hasIdentifier&: priv.hasProductIdentifier);
269
270 if (!priv.description.isEmpty()
271 || !priv.manufacturer.isEmpty()
272 || !priv.serialNumber.isEmpty()
273 || priv.hasVendorIdentifier
274 || priv.hasProductIdentifier) {
275 break;
276 }
277 } while (targetDir.cdUp());
278
279 serialPortInfoList.append(t: priv);
280 }
281
282 ok = true;
283 return serialPortInfoList;
284}
285
286struct udev_deleter {
287 void operator()(struct ::udev *pointer) const
288 {
289 ::udev_unref(udev: pointer);
290 }
291 void operator()(struct ::udev_enumerate *pointer) const
292 {
293 ::udev_enumerate_unref(udev_enumerate: pointer);
294 }
295 void operator()(struct ::udev_device *pointer) const
296 {
297 ::udev_device_unref(udev_device: pointer);
298 }
299};
300template <typename T>
301using udev_ptr = std::unique_ptr<T, udev_deleter>;
302
303#ifndef LINK_LIBUDEV
304 Q_GLOBAL_STATIC(QLibrary, udevLibrary)
305#endif
306
307static QString deviceProperty(struct ::udev_device *dev, const char *name)
308{
309 return QString::fromLatin1(ba: ::udev_device_get_property_value(udev_device: dev, key: name));
310}
311
312static QString deviceDriver(struct ::udev_device *dev)
313{
314 return QString::fromLatin1(ba: ::udev_device_get_driver(udev_device: dev));
315}
316
317static QString deviceDescription(struct ::udev_device *dev)
318{
319 return deviceProperty(dev, name: "ID_MODEL").replace(before: QLatin1Char('_'), after: QLatin1Char(' '));
320}
321
322static QString deviceManufacturer(struct ::udev_device *dev)
323{
324 return deviceProperty(dev, name: "ID_VENDOR").replace(before: QLatin1Char('_'), after: QLatin1Char(' '));
325}
326
327static quint16 deviceProductIdentifier(struct ::udev_device *dev, bool &hasIdentifier)
328{
329 return deviceProperty(dev, name: "ID_MODEL_ID").toInt(ok: &hasIdentifier, base: 16);
330}
331
332static quint16 deviceVendorIdentifier(struct ::udev_device *dev, bool &hasIdentifier)
333{
334 return deviceProperty(dev, name: "ID_VENDOR_ID").toInt(ok: &hasIdentifier, base: 16);
335}
336
337static QString deviceSerialNumber(struct ::udev_device *dev)
338{
339 return deviceProperty(dev,name: "ID_SERIAL_SHORT");
340}
341
342static QString deviceName(struct ::udev_device *dev)
343{
344 return QString::fromLatin1(ba: ::udev_device_get_sysname(udev_device: dev));
345}
346
347static QString deviceLocation(struct ::udev_device *dev)
348{
349 return QString::fromLatin1(ba: ::udev_device_get_devnode(udev_device: dev));
350}
351
352QList<QSerialPortInfo> availablePortsByUdev(bool &ok)
353{
354 ok = false;
355
356#ifndef LINK_LIBUDEV
357 static bool symbolsResolved = resolveSymbols(udevLibrary());
358 if (!symbolsResolved)
359 return QList<QSerialPortInfo>();
360#endif
361
362 const udev_ptr<struct ::udev> udev(::udev_new());
363
364 if (!udev)
365 return QList<QSerialPortInfo>();
366
367 const udev_ptr<udev_enumerate> enumerate(::udev_enumerate_new(udev: udev.get()));
368
369 if (!enumerate)
370 return QList<QSerialPortInfo>();
371
372 ::udev_enumerate_add_match_subsystem(udev_enumerate: enumerate.get(), subsystem: "tty");
373 ::udev_enumerate_scan_devices(udev_enumerate: enumerate.get());
374
375 udev_list_entry *devices = ::udev_enumerate_get_list_entry(udev_enumerate: enumerate.get());
376
377 QList<QSerialPortInfo> serialPortInfoList;
378 udev_list_entry *dev_list_entry;
379 udev_list_entry_foreach(dev_list_entry, devices) {
380
381 ok = true;
382
383 const udev_ptr<udev_device>
384 dev(::udev_device_new_from_syspath(
385 udev: udev.get(), syspath: ::udev_list_entry_get_name(list_entry: dev_list_entry)));
386
387 if (!dev)
388 continue;
389
390 QSerialPortInfoPrivate priv;
391
392 priv.device = deviceLocation(dev: dev.get());
393 priv.portName = deviceName(dev: dev.get());
394
395 udev_device *parentdev = ::udev_device_get_parent(udev_device: dev.get());
396
397 if (parentdev) {
398 const QString driverName = deviceDriver(dev: parentdev);
399 if (isSerial8250Driver(driverName) && !isValidSerial8250(systemLocation: priv.device))
400 continue;
401 priv.description = deviceDescription(dev: dev.get());
402 priv.manufacturer = deviceManufacturer(dev: dev.get());
403 priv.serialNumber = deviceSerialNumber(dev: dev.get());
404 priv.vendorIdentifier = deviceVendorIdentifier(dev: dev.get(), hasIdentifier&: priv.hasVendorIdentifier);
405 priv.productIdentifier = deviceProductIdentifier(dev: dev.get(), hasIdentifier&: priv.hasProductIdentifier);
406 } else {
407 if (!isRfcommDevice(portName: priv.portName)
408 && !isVirtualNullModemDevice(portName: priv.portName)
409 && !isGadgetDevice(portName: priv.portName)) {
410 continue;
411 }
412 }
413
414 serialPortInfoList.append(t: priv);
415 }
416
417 return serialPortInfoList;
418}
419
420QList<QSerialPortInfo> QSerialPortInfo::availablePorts()
421{
422 bool ok = false;
423 QList<QSerialPortInfo> serialPortInfoList;
424
425 const bool skipUdevLookup = qEnvironmentVariableIsSet(varName: SkipUdevEnvVarName);
426 if (!skipUdevLookup)
427 serialPortInfoList = availablePortsByUdev(ok);
428
429#ifdef Q_OS_LINUX
430 if (!ok)
431 serialPortInfoList = availablePortsBySysfs(ok);
432#endif
433
434 if (!ok)
435 serialPortInfoList = availablePortsByFiltersOfDevices(ok);
436
437 return serialPortInfoList;
438}
439
440QString QSerialPortInfoPrivate::portNameToSystemLocation(const QString &source)
441{
442 return (source.startsWith(c: QLatin1Char('/'))
443 || source.startsWith(s: QLatin1String("./"))
444 || source.startsWith(s: QLatin1String("../")))
445 ? source : (QLatin1String("/dev/") + source);
446}
447
448QString QSerialPortInfoPrivate::portNameFromSystemLocation(const QString &source)
449{
450 return source.startsWith(s: QLatin1String("/dev/"))
451 ? source.mid(position: 5) : source;
452}
453
454QT_END_NAMESPACE
455

source code of qtserialport/src/serialport/qserialportinfo_unix.cpp