blob: 105d10af90cc70cdae0c7d6c308a2ee59c3b9e7b [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2011-2012 Denis Shienkov <denis.shienkov@gmail.com>
** Copyright (C) 2011 Sergey Belyashov <Sergey.Belyashov@gmail.com>
** Copyright (C) 2012 Laszlo Papp <lpapp@kde.org>
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtSerialPort module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qserialportinfo.h"
#include "qserialportinfo_p.h"
#include "qserialport_p.h"
#include <QtCore/qlockfile.h>
#include <QtCore/qfile.h>
#include <QtCore/qdir.h>
#include <QtCore/qscopedpointer.h>
#include <private/qcore_unix_p.h>
#include <errno.h>
#include <sys/types.h> // kill
#include <signal.h> // kill
#include "qtudev_p.h"
QT_BEGIN_NAMESPACE
static QStringList filteredDeviceFilePaths()
{
static const QStringList deviceFileNameFilterList = QStringList()
#ifdef Q_OS_LINUX
<< QStringLiteral("ttyS*") // Standard UART 8250 and etc.
<< QStringLiteral("ttyO*") // OMAP UART 8250 and etc.
<< QStringLiteral("ttyUSB*") // Usb/serial converters PL2303 and etc.
<< QStringLiteral("ttyACM*") // CDC_ACM converters (i.e. Mobile Phones).
<< QStringLiteral("ttyGS*") // Gadget serial device (i.e. Mobile Phones with gadget serial driver).
<< QStringLiteral("ttyMI*") // MOXA pci/serial converters.
<< QStringLiteral("ttymxc*") // Motorola IMX serial ports (i.e. Freescale i.MX).
<< QStringLiteral("ttyAMA*") // AMBA serial device for embedded platform on ARM (i.e. Raspberry Pi).
<< QStringLiteral("ttyTHS*") // Serial device for embedded platform on ARM (i.e. Tegra Jetson TK1).
<< QStringLiteral("rfcomm*") // Bluetooth serial device.
<< QStringLiteral("ircomm*") // IrDA serial device.
<< QStringLiteral("tnt*"); // Virtual tty0tty serial device.
#elif defined(Q_OS_FREEBSD)
<< QStringLiteral("cu*");
#elif defined(Q_OS_QNX)
<< QStringLiteral("ser*");
#else
;
#endif
QStringList result;
QDir deviceDir(QStringLiteral("/dev"));
if (deviceDir.exists()) {
deviceDir.setNameFilters(deviceFileNameFilterList);
deviceDir.setFilter(QDir::Files | QDir::System | QDir::NoSymLinks);
QStringList deviceFilePaths;
const auto deviceFileInfos = deviceDir.entryInfoList();
for (const QFileInfo &deviceFileInfo : deviceFileInfos) {
const QString deviceAbsoluteFilePath = deviceFileInfo.absoluteFilePath();
#ifdef Q_OS_FREEBSD
// it is a quick workaround to skip the non-serial devices
if (deviceAbsoluteFilePath.endsWith(QLatin1String(".init"))
|| deviceAbsoluteFilePath.endsWith(QLatin1String(".lock"))) {
continue;
}
#endif
if (!deviceFilePaths.contains(deviceAbsoluteFilePath)) {
deviceFilePaths.append(deviceAbsoluteFilePath);
result.append(deviceAbsoluteFilePath);
}
}
}
return result;
}
QList<QSerialPortInfo> availablePortsByFiltersOfDevices(bool &ok)
{
QList<QSerialPortInfo> serialPortInfoList;
const auto deviceFilePaths = filteredDeviceFilePaths();
for (const QString &deviceFilePath : deviceFilePaths) {
QSerialPortInfoPrivate priv;
priv.device = deviceFilePath;
priv.portName = QSerialPortInfoPrivate::portNameFromSystemLocation(deviceFilePath);
serialPortInfoList.append(priv);
}
ok = true;
return serialPortInfoList;
}
static bool isSerial8250Driver(const QString &driverName)
{
return (driverName == QLatin1String("serial8250"));
}
static bool isValidSerial8250(const QString &systemLocation)
{
#ifdef Q_OS_LINUX
const mode_t flags = O_RDWR | O_NONBLOCK | O_NOCTTY;
const int fd = qt_safe_open(systemLocation.toLocal8Bit().constData(), flags);
if (fd != -1) {
struct serial_struct serinfo;
const int retval = ::ioctl(fd, TIOCGSERIAL, &serinfo);
qt_safe_close(fd);
if (retval != -1 && serinfo.type != PORT_UNKNOWN)
return true;
}
#else
Q_UNUSED(systemLocation);
#endif
return false;
}
static bool isRfcommDevice(const QString &portName)
{
if (!portName.startsWith(QLatin1String("rfcomm")))
return false;
bool ok;
const int portNumber = portName.midRef(6).toInt(&ok);
if (!ok || (portNumber < 0) || (portNumber > 255))
return false;
return true;
}
// provided by the tty0tty driver
static bool isVirtualNullModemDevice(const QString &portName)
{
return portName.startsWith(QLatin1String("tnt"));
}
// provided by the g_serial driver
static bool isGadgetDevice(const QString &portName)
{
return portName.startsWith(QLatin1String("ttyGS"));
}
static QString ueventProperty(const QDir &targetDir, const QByteArray &pattern)
{
QFile f(QFileInfo(targetDir, QStringLiteral("uevent")).absoluteFilePath());
if (!f.open(QIODevice::ReadOnly | QIODevice::Text))
return QString();
const QByteArray content = f.readAll();
const int firstbound = content.indexOf(pattern);
if (firstbound == -1)
return QString();
const int lastbound = content.indexOf('\n', firstbound);
return QString::fromLatin1(
content.mid(firstbound + pattern.size(),
lastbound - firstbound - pattern.size()))
.simplified();
}
static QString deviceName(const QDir &targetDir)
{
return ueventProperty(targetDir, "DEVNAME=");
}
static QString deviceDriver(const QDir &targetDir)
{
const QDir deviceDir(targetDir.absolutePath() + QLatin1String("/device"));
return ueventProperty(deviceDir, "DRIVER=");
}
static QString deviceProperty(const QString &targetFilePath)
{
QFile f(targetFilePath);
if (!f.open(QIODevice::ReadOnly | QIODevice::Text))
return QString();
return QString::fromLatin1(f.readAll()).simplified();
}
static QString deviceDescription(const QDir &targetDir)
{
return deviceProperty(QFileInfo(targetDir, QStringLiteral("product")).absoluteFilePath());
}
static QString deviceManufacturer(const QDir &targetDir)
{
return deviceProperty(QFileInfo(targetDir, QStringLiteral("manufacturer")).absoluteFilePath());
}
static quint16 deviceProductIdentifier(const QDir &targetDir, bool &hasIdentifier)
{
QString result = deviceProperty(QFileInfo(targetDir, QStringLiteral("idProduct")).absoluteFilePath());
if (result.isEmpty())
result = deviceProperty(QFileInfo(targetDir, QStringLiteral("device")).absoluteFilePath());
return result.toInt(&hasIdentifier, 16);
}
static quint16 deviceVendorIdentifier(const QDir &targetDir, bool &hasIdentifier)
{
QString result = deviceProperty(QFileInfo(targetDir, QStringLiteral("idVendor")).absoluteFilePath());
if (result.isEmpty())
result = deviceProperty(QFileInfo(targetDir, QStringLiteral("vendor")).absoluteFilePath());
return result.toInt(&hasIdentifier, 16);
}
static QString deviceSerialNumber(const QDir &targetDir)
{
return deviceProperty(QFileInfo(targetDir, QStringLiteral("serial")).absoluteFilePath());
}
QList<QSerialPortInfo> availablePortsBySysfs(bool &ok)
{
QDir ttySysClassDir(QStringLiteral("/sys/class/tty"));
if (!(ttySysClassDir.exists() && ttySysClassDir.isReadable())) {
ok = false;
return QList<QSerialPortInfo>();
}
QList<QSerialPortInfo> serialPortInfoList;
ttySysClassDir.setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
const auto fileInfos = ttySysClassDir.entryInfoList();
for (const QFileInfo &fileInfo : fileInfos) {
if (!fileInfo.isSymLink())
continue;
QDir targetDir(fileInfo.symLinkTarget());
QSerialPortInfoPrivate priv;
priv.portName = deviceName(targetDir);
if (priv.portName.isEmpty())
continue;
const QString driverName = deviceDriver(targetDir);
if (driverName.isEmpty()) {
if (!isRfcommDevice(priv.portName)
&& !isVirtualNullModemDevice(priv.portName)
&& !isGadgetDevice(priv.portName)) {
continue;
}
}
priv.device = QSerialPortInfoPrivate::portNameToSystemLocation(priv.portName);
if (isSerial8250Driver(driverName) && !isValidSerial8250(priv.device))
continue;
do {
if (priv.description.isEmpty())
priv.description = deviceDescription(targetDir);
if (priv.manufacturer.isEmpty())
priv.manufacturer = deviceManufacturer(targetDir);
if (priv.serialNumber.isEmpty())
priv.serialNumber = deviceSerialNumber(targetDir);
if (!priv.hasVendorIdentifier)
priv.vendorIdentifier = deviceVendorIdentifier(targetDir, priv.hasVendorIdentifier);
if (!priv.hasProductIdentifier)
priv.productIdentifier = deviceProductIdentifier(targetDir, priv.hasProductIdentifier);
if (!priv.description.isEmpty()
|| !priv.manufacturer.isEmpty()
|| !priv.serialNumber.isEmpty()
|| priv.hasVendorIdentifier
|| priv.hasProductIdentifier) {
break;
}
} while (targetDir.cdUp());
serialPortInfoList.append(priv);
}
ok = true;
return serialPortInfoList;
}
struct ScopedPointerUdevDeleter
{
static inline void cleanup(struct ::udev *pointer)
{
::udev_unref(pointer);
}
};
struct ScopedPointerUdevEnumeratorDeleter
{
static inline void cleanup(struct ::udev_enumerate *pointer)
{
::udev_enumerate_unref(pointer);
}
};
struct ScopedPointerUdevDeviceDeleter
{
static inline void cleanup(struct ::udev_device *pointer)
{
::udev_device_unref(pointer);
}
};
#ifndef LINK_LIBUDEV
Q_GLOBAL_STATIC(QLibrary, udevLibrary)
#endif
static QString deviceProperty(struct ::udev_device *dev, const char *name)
{
return QString::fromLatin1(::udev_device_get_property_value(dev, name));
}
static QString deviceDriver(struct ::udev_device *dev)
{
return QString::fromLatin1(::udev_device_get_driver(dev));
}
static QString deviceDescription(struct ::udev_device *dev)
{
return deviceProperty(dev, "ID_MODEL").replace(QLatin1Char('_'), QLatin1Char(' '));
}
static QString deviceManufacturer(struct ::udev_device *dev)
{
return deviceProperty(dev, "ID_VENDOR").replace(QLatin1Char('_'), QLatin1Char(' '));
}
static quint16 deviceProductIdentifier(struct ::udev_device *dev, bool &hasIdentifier)
{
return deviceProperty(dev, "ID_MODEL_ID").toInt(&hasIdentifier, 16);
}
static quint16 deviceVendorIdentifier(struct ::udev_device *dev, bool &hasIdentifier)
{
return deviceProperty(dev, "ID_VENDOR_ID").toInt(&hasIdentifier, 16);
}
static QString deviceSerialNumber(struct ::udev_device *dev)
{
return deviceProperty(dev,"ID_SERIAL_SHORT");
}
static QString deviceName(struct ::udev_device *dev)
{
return QString::fromLatin1(::udev_device_get_sysname(dev));
}
static QString deviceLocation(struct ::udev_device *dev)
{
return QString::fromLatin1(::udev_device_get_devnode(dev));
}
QList<QSerialPortInfo> availablePortsByUdev(bool &ok)
{
ok = false;
#ifndef LINK_LIBUDEV
static bool symbolsResolved = resolveSymbols(udevLibrary());
if (!symbolsResolved)
return QList<QSerialPortInfo>();
#endif
QScopedPointer<struct ::udev, ScopedPointerUdevDeleter> udev(::udev_new());
if (!udev)
return QList<QSerialPortInfo>();
QScopedPointer<udev_enumerate, ScopedPointerUdevEnumeratorDeleter>
enumerate(::udev_enumerate_new(udev.data()));
if (!enumerate)
return QList<QSerialPortInfo>();
::udev_enumerate_add_match_subsystem(enumerate.data(), "tty");
::udev_enumerate_scan_devices(enumerate.data());
udev_list_entry *devices = ::udev_enumerate_get_list_entry(enumerate.data());
QList<QSerialPortInfo> serialPortInfoList;
udev_list_entry *dev_list_entry;
udev_list_entry_foreach(dev_list_entry, devices) {
ok = true;
QScopedPointer<udev_device, ScopedPointerUdevDeviceDeleter>
dev(::udev_device_new_from_syspath(
udev.data(), ::udev_list_entry_get_name(dev_list_entry)));
if (!dev)
return serialPortInfoList;
QSerialPortInfoPrivate priv;
priv.device = deviceLocation(dev.data());
priv.portName = deviceName(dev.data());
udev_device *parentdev = ::udev_device_get_parent(dev.data());
if (parentdev) {
const QString driverName = deviceDriver(parentdev);
if (isSerial8250Driver(driverName) && !isValidSerial8250(priv.device))
continue;
priv.description = deviceDescription(dev.data());
priv.manufacturer = deviceManufacturer(dev.data());
priv.serialNumber = deviceSerialNumber(dev.data());
priv.vendorIdentifier = deviceVendorIdentifier(dev.data(), priv.hasVendorIdentifier);
priv.productIdentifier = deviceProductIdentifier(dev.data(), priv.hasProductIdentifier);
} else {
if (!isRfcommDevice(priv.portName)
&& !isVirtualNullModemDevice(priv.portName)
&& !isGadgetDevice(priv.portName)) {
continue;
}
}
serialPortInfoList.append(priv);
}
return serialPortInfoList;
}
QList<QSerialPortInfo> QSerialPortInfo::availablePorts()
{
bool ok;
QList<QSerialPortInfo> serialPortInfoList = availablePortsByUdev(ok);
#ifdef Q_OS_LINUX
if (!ok)
serialPortInfoList = availablePortsBySysfs(ok);
#endif
if (!ok)
serialPortInfoList = availablePortsByFiltersOfDevices(ok);
return serialPortInfoList;
}
#if QT_DEPRECATED_SINCE(5, 6)
bool QSerialPortInfo::isBusy() const
{
QString lockFilePath = serialPortLockFilePath(portName());
if (lockFilePath.isEmpty())
return false;
QFile reader(lockFilePath);
if (!reader.open(QIODevice::ReadOnly))
return false;
QByteArray pidLine = reader.readLine();
pidLine.chop(1);
if (pidLine.isEmpty())
return false;
qint64 pid = pidLine.toLongLong();
if (pid && (::kill(pid, 0) == -1) && (errno == ESRCH))
return false; // PID doesn't exist anymore
return true;
}
#endif // QT_DEPRECATED_SINCE(5, 6)
#if QT_DEPRECATED_SINCE(5, 2)
bool QSerialPortInfo::isValid() const
{
QFile f(systemLocation());
return f.exists();
}
#endif // QT_DEPRECATED_SINCE(5, 2)
QString QSerialPortInfoPrivate::portNameToSystemLocation(const QString &source)
{
return (source.startsWith(QLatin1Char('/'))
|| source.startsWith(QLatin1String("./"))
|| source.startsWith(QLatin1String("../")))
? source : (QLatin1String("/dev/") + source);
}
QString QSerialPortInfoPrivate::portNameFromSystemLocation(const QString &source)
{
return source.startsWith(QLatin1String("/dev/"))
? source.mid(5) : source;
}
QT_END_NAMESPACE