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 | |
24 | QT_BEGIN_NAMESPACE |
25 | |
26 | static 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 | |
80 | QList<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 | |
96 | static bool isSerial8250Driver(const QString &driverName) |
97 | { |
98 | return (driverName == QLatin1String("serial8250" )); |
99 | } |
100 | |
101 | static 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 | |
119 | static 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 |
132 | static bool isVirtualNullModemDevice(const QString &portName) |
133 | { |
134 | return portName.startsWith(s: QLatin1String("tnt" )); |
135 | } |
136 | |
137 | // provided by the g_serial driver |
138 | static bool isGadgetDevice(const QString &portName) |
139 | { |
140 | return portName.startsWith(s: QLatin1String("ttyGS" )); |
141 | } |
142 | |
143 | static 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 | |
162 | static QString deviceName(const QDir &targetDir) |
163 | { |
164 | return ueventProperty(targetDir, pattern: "DEVNAME=" ); |
165 | } |
166 | |
167 | static QString deviceDriver(const QDir &targetDir) |
168 | { |
169 | const QDir deviceDir(targetDir.absolutePath() + QLatin1String("/device" )); |
170 | return ueventProperty(targetDir: deviceDir, pattern: "DRIVER=" ); |
171 | } |
172 | |
173 | static 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 | |
181 | static QString deviceDescription(const QDir &targetDir) |
182 | { |
183 | return deviceProperty(targetFilePath: QFileInfo(targetDir, QStringLiteral("product" )).absoluteFilePath()); |
184 | } |
185 | |
186 | static QString deviceManufacturer(const QDir &targetDir) |
187 | { |
188 | return deviceProperty(targetFilePath: QFileInfo(targetDir, QStringLiteral("manufacturer" )).absoluteFilePath()); |
189 | } |
190 | |
191 | static 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 | |
199 | static 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 | |
207 | static QString deviceSerialNumber(const QDir &targetDir) |
208 | { |
209 | return deviceProperty(targetFilePath: QFileInfo(targetDir, QStringLiteral("serial" )).absoluteFilePath()); |
210 | } |
211 | |
212 | QList<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 | |
281 | struct 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 | }; |
295 | template <typename T> |
296 | using udev_ptr = std::unique_ptr<T, udev_deleter>; |
297 | |
298 | #ifndef LINK_LIBUDEV |
299 | Q_GLOBAL_STATIC(QLibrary, udevLibrary) |
300 | #endif |
301 | |
302 | static 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 | |
307 | static QString deviceDriver(struct ::udev_device *dev) |
308 | { |
309 | return QString::fromLatin1(ba: ::udev_device_get_driver(udev_device: dev)); |
310 | } |
311 | |
312 | static QString deviceDescription(struct ::udev_device *dev) |
313 | { |
314 | return deviceProperty(dev, name: "ID_MODEL" ).replace(before: QLatin1Char('_'), after: QLatin1Char(' ')); |
315 | } |
316 | |
317 | static QString deviceManufacturer(struct ::udev_device *dev) |
318 | { |
319 | return deviceProperty(dev, name: "ID_VENDOR" ).replace(before: QLatin1Char('_'), after: QLatin1Char(' ')); |
320 | } |
321 | |
322 | static quint16 deviceProductIdentifier(struct ::udev_device *dev, bool &hasIdentifier) |
323 | { |
324 | return deviceProperty(dev, name: "ID_MODEL_ID" ).toInt(ok: &hasIdentifier, base: 16); |
325 | } |
326 | |
327 | static quint16 deviceVendorIdentifier(struct ::udev_device *dev, bool &hasIdentifier) |
328 | { |
329 | return deviceProperty(dev, name: "ID_VENDOR_ID" ).toInt(ok: &hasIdentifier, base: 16); |
330 | } |
331 | |
332 | static QString deviceSerialNumber(struct ::udev_device *dev) |
333 | { |
334 | return deviceProperty(dev,name: "ID_SERIAL_SHORT" ); |
335 | } |
336 | |
337 | static QString deviceName(struct ::udev_device *dev) |
338 | { |
339 | return QString::fromLatin1(ba: ::udev_device_get_sysname(udev_device: dev)); |
340 | } |
341 | |
342 | static QString deviceLocation(struct ::udev_device *dev) |
343 | { |
344 | return QString::fromLatin1(ba: ::udev_device_get_devnode(udev_device: dev)); |
345 | } |
346 | |
347 | QList<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 | |
415 | QList<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 | |
432 | QString 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 | |
440 | QString QSerialPortInfoPrivate::portNameFromSystemLocation(const QString &source) |
441 | { |
442 | return source.startsWith(s: QLatin1String("/dev/" )) |
443 | ? source.mid(position: 5) : source; |
444 | } |
445 | |
446 | QT_END_NAMESPACE |
447 | |