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

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