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 int firstbound = content.indexOf(bv: pattern);
155 if (firstbound == -1)
156 return QString();
157
158 const int lastbound = content.indexOf(ch: '\n', from: firstbound);
159 return QString::fromLatin1(
160 ba: content.mid(index: firstbound + pattern.size(),
161 len: lastbound - firstbound - pattern.size()))
162 .simplified();
163}
164
165static QString deviceName(const QDir &targetDir)
166{
167 return ueventProperty(targetDir, pattern: "DEVNAME=");
168}
169
170static QString deviceDriver(const QDir &targetDir)
171{
172 const QDir deviceDir(targetDir.absolutePath() + QLatin1String("/device"));
173 return ueventProperty(targetDir: deviceDir, pattern: "DRIVER=");
174}
175
176static QString deviceProperty(const QString &targetFilePath)
177{
178 QFile f(targetFilePath);
179 if (!f.open(flags: QIODevice::ReadOnly | QIODevice::Text))
180 return QString();
181 return QString::fromLatin1(ba: f.readAll()).simplified();
182}
183
184static QString deviceDescription(const QDir &targetDir)
185{
186 return deviceProperty(targetFilePath: QFileInfo(targetDir, QStringLiteral("product")).absoluteFilePath());
187}
188
189static QString deviceManufacturer(const QDir &targetDir)
190{
191 return deviceProperty(targetFilePath: QFileInfo(targetDir, QStringLiteral("manufacturer")).absoluteFilePath());
192}
193
194static quint16 deviceProductIdentifier(const QDir &targetDir, bool &hasIdentifier)
195{
196 QString result = deviceProperty(targetFilePath: QFileInfo(targetDir, QStringLiteral("idProduct")).absoluteFilePath());
197 if (result.isEmpty())
198 result = deviceProperty(targetFilePath: QFileInfo(targetDir, QStringLiteral("device")).absoluteFilePath());
199 return result.toInt(ok: &hasIdentifier, base: 16);
200}
201
202static quint16 deviceVendorIdentifier(const QDir &targetDir, bool &hasIdentifier)
203{
204 QString result = deviceProperty(targetFilePath: QFileInfo(targetDir, QStringLiteral("idVendor")).absoluteFilePath());
205 if (result.isEmpty())
206 result = deviceProperty(targetFilePath: QFileInfo(targetDir, QStringLiteral("vendor")).absoluteFilePath());
207 return result.toInt(ok: &hasIdentifier, base: 16);
208}
209
210static QString deviceSerialNumber(const QDir &targetDir)
211{
212 return deviceProperty(targetFilePath: QFileInfo(targetDir, QStringLiteral("serial")).absoluteFilePath());
213}
214
215QList<QSerialPortInfo> availablePortsBySysfs(bool &ok)
216{
217 QDir ttySysClassDir(QStringLiteral("/sys/class/tty"));
218
219 if (!(ttySysClassDir.exists() && ttySysClassDir.isReadable())) {
220 ok = false;
221 return QList<QSerialPortInfo>();
222 }
223
224 QList<QSerialPortInfo> serialPortInfoList;
225 ttySysClassDir.setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
226 const auto fileInfos = ttySysClassDir.entryInfoList();
227 for (const QFileInfo &fileInfo : fileInfos) {
228 if (!fileInfo.isSymLink())
229 continue;
230
231 QDir targetDir(fileInfo.symLinkTarget());
232
233 QSerialPortInfoPrivate priv;
234
235 priv.portName = deviceName(targetDir);
236 if (priv.portName.isEmpty())
237 continue;
238
239 const QString driverName = deviceDriver(targetDir);
240 if (driverName.isEmpty()) {
241 if (!isRfcommDevice(portName: priv.portName)
242 && !isVirtualNullModemDevice(portName: priv.portName)
243 && !isGadgetDevice(portName: priv.portName)) {
244 continue;
245 }
246 }
247
248 priv.device = QSerialPortInfoPrivate::portNameToSystemLocation(source: priv.portName);
249 if (isSerial8250Driver(driverName) && !isValidSerial8250(systemLocation: priv.device))
250 continue;
251
252 do {
253 if (priv.description.isEmpty())
254 priv.description = deviceDescription(targetDir);
255
256 if (priv.manufacturer.isEmpty())
257 priv.manufacturer = deviceManufacturer(targetDir);
258
259 if (priv.serialNumber.isEmpty())
260 priv.serialNumber = deviceSerialNumber(targetDir);
261
262 if (!priv.hasVendorIdentifier)
263 priv.vendorIdentifier = deviceVendorIdentifier(targetDir, hasIdentifier&: priv.hasVendorIdentifier);
264
265 if (!priv.hasProductIdentifier)
266 priv.productIdentifier = deviceProductIdentifier(targetDir, hasIdentifier&: priv.hasProductIdentifier);
267
268 if (!priv.description.isEmpty()
269 || !priv.manufacturer.isEmpty()
270 || !priv.serialNumber.isEmpty()
271 || priv.hasVendorIdentifier
272 || priv.hasProductIdentifier) {
273 break;
274 }
275 } while (targetDir.cdUp());
276
277 serialPortInfoList.append(t: priv);
278 }
279
280 ok = true;
281 return serialPortInfoList;
282}
283
284struct udev_deleter {
285 void operator()(struct ::udev *pointer) const
286 {
287 ::udev_unref(udev: pointer);
288 }
289 void operator()(struct ::udev_enumerate *pointer) const
290 {
291 ::udev_enumerate_unref(udev_enumerate: pointer);
292 }
293 void operator()(struct ::udev_device *pointer) const
294 {
295 ::udev_device_unref(udev_device: pointer);
296 }
297};
298template <typename T>
299using udev_ptr = std::unique_ptr<T, udev_deleter>;
300
301#ifndef LINK_LIBUDEV
302 Q_GLOBAL_STATIC(QLibrary, udevLibrary)
303#endif
304
305static QString deviceProperty(struct ::udev_device *dev, const char *name)
306{
307 return QString::fromLatin1(ba: ::udev_device_get_property_value(udev_device: dev, key: name));
308}
309
310static QString deviceDriver(struct ::udev_device *dev)
311{
312 return QString::fromLatin1(ba: ::udev_device_get_driver(udev_device: dev));
313}
314
315static QString deviceDescription(struct ::udev_device *dev)
316{
317 return deviceProperty(dev, name: "ID_MODEL").replace(before: QLatin1Char('_'), after: QLatin1Char(' '));
318}
319
320static QString deviceManufacturer(struct ::udev_device *dev)
321{
322 return deviceProperty(dev, name: "ID_VENDOR").replace(before: QLatin1Char('_'), after: QLatin1Char(' '));
323}
324
325static quint16 deviceProductIdentifier(struct ::udev_device *dev, bool &hasIdentifier)
326{
327 return deviceProperty(dev, name: "ID_MODEL_ID").toInt(ok: &hasIdentifier, base: 16);
328}
329
330static quint16 deviceVendorIdentifier(struct ::udev_device *dev, bool &hasIdentifier)
331{
332 return deviceProperty(dev, name: "ID_VENDOR_ID").toInt(ok: &hasIdentifier, base: 16);
333}
334
335static QString deviceSerialNumber(struct ::udev_device *dev)
336{
337 return deviceProperty(dev,name: "ID_SERIAL_SHORT");
338}
339
340static QString deviceName(struct ::udev_device *dev)
341{
342 return QString::fromLatin1(ba: ::udev_device_get_sysname(udev_device: dev));
343}
344
345static QString deviceLocation(struct ::udev_device *dev)
346{
347 return QString::fromLatin1(ba: ::udev_device_get_devnode(udev_device: dev));
348}
349
350QList<QSerialPortInfo> availablePortsByUdev(bool &ok)
351{
352 ok = false;
353
354#ifndef LINK_LIBUDEV
355 static bool symbolsResolved = resolveSymbols(udevLibrary());
356 if (!symbolsResolved)
357 return QList<QSerialPortInfo>();
358#endif
359
360 const udev_ptr<struct ::udev> udev(::udev_new());
361
362 if (!udev)
363 return QList<QSerialPortInfo>();
364
365 const udev_ptr<udev_enumerate> enumerate(::udev_enumerate_new(udev: udev.get()));
366
367 if (!enumerate)
368 return QList<QSerialPortInfo>();
369
370 ::udev_enumerate_add_match_subsystem(udev_enumerate: enumerate.get(), subsystem: "tty");
371 ::udev_enumerate_scan_devices(udev_enumerate: enumerate.get());
372
373 udev_list_entry *devices = ::udev_enumerate_get_list_entry(udev_enumerate: enumerate.get());
374
375 QList<QSerialPortInfo> serialPortInfoList;
376 udev_list_entry *dev_list_entry;
377 udev_list_entry_foreach(dev_list_entry, devices) {
378
379 ok = true;
380
381 const udev_ptr<udev_device>
382 dev(::udev_device_new_from_syspath(
383 udev: udev.get(), syspath: ::udev_list_entry_get_name(list_entry: dev_list_entry)));
384
385 if (!dev)
386 return serialPortInfoList;
387
388 QSerialPortInfoPrivate priv;
389
390 priv.device = deviceLocation(dev: dev.get());
391 priv.portName = deviceName(dev: dev.get());
392
393 udev_device *parentdev = ::udev_device_get_parent(udev_device: dev.get());
394
395 if (parentdev) {
396 const QString driverName = deviceDriver(dev: parentdev);
397 if (isSerial8250Driver(driverName) && !isValidSerial8250(systemLocation: priv.device))
398 continue;
399 priv.description = deviceDescription(dev: dev.get());
400 priv.manufacturer = deviceManufacturer(dev: dev.get());
401 priv.serialNumber = deviceSerialNumber(dev: dev.get());
402 priv.vendorIdentifier = deviceVendorIdentifier(dev: dev.get(), hasIdentifier&: priv.hasVendorIdentifier);
403 priv.productIdentifier = deviceProductIdentifier(dev: dev.get(), hasIdentifier&: priv.hasProductIdentifier);
404 } else {
405 if (!isRfcommDevice(portName: priv.portName)
406 && !isVirtualNullModemDevice(portName: priv.portName)
407 && !isGadgetDevice(portName: priv.portName)) {
408 continue;
409 }
410 }
411
412 serialPortInfoList.append(t: priv);
413 }
414
415 return serialPortInfoList;
416}
417
418QList<QSerialPortInfo> QSerialPortInfo::availablePorts()
419{
420 bool ok = false;
421 QList<QSerialPortInfo> serialPortInfoList;
422
423 const bool skipUdevLookup = qEnvironmentVariableIsSet(varName: SkipUdevEnvVarName);
424 if (!skipUdevLookup)
425 serialPortInfoList = availablePortsByUdev(ok);
426
427#ifdef Q_OS_LINUX
428 if (!ok)
429 serialPortInfoList = availablePortsBySysfs(ok);
430#endif
431
432 if (!ok)
433 serialPortInfoList = availablePortsByFiltersOfDevices(ok);
434
435 return serialPortInfoList;
436}
437
438QString QSerialPortInfoPrivate::portNameToSystemLocation(const QString &source)
439{
440 return (source.startsWith(c: QLatin1Char('/'))
441 || source.startsWith(s: QLatin1String("./"))
442 || source.startsWith(s: QLatin1String("../")))
443 ? source : (QLatin1String("/dev/") + source);
444}
445
446QString QSerialPortInfoPrivate::portNameFromSystemLocation(const QString &source)
447{
448 return source.startsWith(s: QLatin1String("/dev/"))
449 ? source.mid(position: 5) : source;
450}
451
452QT_END_NAMESPACE
453

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