From 9b016e0e28a6ff41d62172ac85c4cccce5477444 Mon Sep 17 00:00:00 2001 From: Denis Shienkov Date: Tue, 11 Aug 2015 19:17:14 +0300 Subject: Obtain detail information in FreeBSD Implementation based on the sysctl(3) system call, in which undocumented features are used. This implementation tested only with the USB devices, because there is no opportunity to check it as well with an other stuff. Also still it is impossible to extract the description and the manufacturer properties from the source, because they are merged inside of one string. Tested with the USB devices (FTDI, Prolific, ZTE, Samsung) using FreeBSD v10. (cherry-picked from 3e6308409dd679389e6231745e4d919d68d6487c) Change-Id: I0091e4db70bfdfd4da199dd9d89dc78cf8f632b0 Reviewed-by: Denis Shienkov --- src/serialport/qserialportinfo.h | 3 + src/serialport/qserialportinfo_unix.cpp | 288 ++++++++++++++++++++++++++++++++ 2 files changed, 291 insertions(+) diff --git a/src/serialport/qserialportinfo.h b/src/serialport/qserialportinfo.h index 7aa98d10..f7e719fa 100644 --- a/src/serialport/qserialportinfo.h +++ b/src/serialport/qserialportinfo.h @@ -92,6 +92,9 @@ private: QSerialPortInfo(const QSerialPortInfoPrivate &dd); friend QList availablePortsByUdev(bool &ok); friend QList availablePortsBySysfs(bool &ok); +#ifdef Q_OS_FREEBSD + friend QList availablePortsBySysctl(bool &ok); +#endif friend QList availablePortsByFiltersOfDevices(bool &ok); QScopedPointer d_ptr; }; diff --git a/src/serialport/qserialportinfo_unix.cpp b/src/serialport/qserialportinfo_unix.cpp index b522c3dd..7e64bfc5 100644 --- a/src/serialport/qserialportinfo_unix.cpp +++ b/src/serialport/qserialportinfo_unix.cpp @@ -50,12 +50,21 @@ #include #include +#ifdef Q_OS_FREEBSD +#include +#include +#endif + #include #include #include // kill #include // kill +#ifdef Q_OS_FREEBSD +#include // sysctl, sysctlnametomib +#endif + #include "qtudev_p.h" QT_BEGIN_NAMESPACE @@ -149,6 +158,280 @@ static bool isValidSerial8250(const QString &systemLocation) return false; } +#ifdef Q_OS_FREEBSD + +static QString deviceProperty(const QString &pnpinfo, const QByteArray &pattern) +{ + const int firstbound = pnpinfo.indexOf(QLatin1String(pattern)); + if (firstbound == -1) + return QString(); + const int lastbound = pnpinfo.indexOf(QLatin1Char(' '), firstbound); + return pnpinfo.mid(firstbound + pattern.size(), lastbound - firstbound - pattern.size()); +} + +static QString deviceName(const QString &pnpinfo) +{ + return deviceProperty(pnpinfo, "ttyname="); +} + +static QString deviceCount(const QString &pnpinfo) +{ + return deviceProperty(pnpinfo, "ttyports="); +} + +static quint16 deviceProductIdentifier(const QString &pnpinfo, bool &hasIdentifier) +{ + QString result = deviceProperty(pnpinfo, "product="); + return result.toInt(&hasIdentifier, 16); +} + +static quint16 deviceVendorIdentifier(const QString &pnpinfo, bool &hasIdentifier) +{ + QString result = deviceProperty(pnpinfo, "vendor="); + return result.toInt(&hasIdentifier, 16); +} + +static QString deviceSerialNumber(const QString &pnpinfo) +{ + QString serialNumber = deviceProperty(pnpinfo, "sernum="); + serialNumber.remove(QLatin1Char('"')); + return serialNumber; +} + +// A 'desc' string contains the both description and manufacturer +// properties, which are not possible to extract from the source +// string. Besides, this string can contains an other information, +// which should be excluded from the result. +static QString deviceDescriptionAndManufacturer(const QString &desc) +{ + const int classindex = desc.indexOf(QLatin1String(", class ")); + if (classindex == -1) + return desc; + return desc.mid(0, classindex); +} + +struct NodeInfo +{ + QString name; + QString value; +}; + +static QVector mibFromName(const QString &name) +{ + size_t mibsize = 0; + if (::sysctlnametomib(name.toLocal8Bit().constData(), Q_NULLPTR, &mibsize) < 0 + || mibsize == 0) { + return QVector(); + } + QVector mib(mibsize); + if (::sysctlnametomib(name.toLocal8Bit().constData(), &mib[0], &mibsize) < 0) + return QVector(); + + return mib; +} + +static QVector nextOid(const QVector &previousOid) +{ + QVector mib; + mib.append(0); // Magic undocumented code (CTL_UNSPEC ?) + mib.append(2); // Magic undocumented code + foreach (int code, previousOid) + mib.append(code); + + size_t requiredLength = 0; + if (::sysctl(&mib[0], mib.count(), Q_NULLPTR, &requiredLength, Q_NULLPTR, 0) < 0) + return QVector(); + const size_t oidLength = requiredLength / sizeof(int); + QVector oid(oidLength, 0); + if (::sysctl(&mib[0], mib.count(), &oid[0], &requiredLength, Q_NULLPTR, 0) < 0) + return QVector(); + + if (previousOid.first() != oid.first()) + return QVector(); + + return oid; +} + +static NodeInfo nodeForOid(const QVector &oid) +{ + QVector mib; + mib.append(0); // Magic undocumented code (CTL_UNSPEC ?) + mib.append(1); // Magic undocumented code + foreach (int code, oid) + mib.append(code); + + // query node name + size_t requiredLength = 0; + if (::sysctl(&mib[0], mib.count(), Q_NULLPTR, &requiredLength, Q_NULLPTR, 0) < 0) + return NodeInfo(); + QByteArray name(requiredLength, 0); + if (::sysctl(&mib[0], mib.count(), name.data(), &requiredLength, Q_NULLPTR, 0) < 0) + return NodeInfo(); + + // query node value + requiredLength = 0; + if (::sysctl(&oid[0], oid.count(), Q_NULLPTR, &requiredLength, Q_NULLPTR, 0) < 0) + return NodeInfo(); + QByteArray value(requiredLength, 0); + if (::sysctl(&oid[0], oid.count(), value.data(), &requiredLength, Q_NULLPTR, 0) < 0) + return NodeInfo(); + + // query value format + mib[1] = 4; // Magic undocumented code + requiredLength = 0; + if (::sysctl(&mib[0], mib.count(), Q_NULLPTR, &requiredLength, Q_NULLPTR, 0) < 0) + return NodeInfo(); + QByteArray buf(requiredLength, 0); + if (::sysctl(&mib[0], mib.count(), buf.data(), &requiredLength, Q_NULLPTR, 0) < 0) + return NodeInfo(); + + QDataStream in(buf); + in.setByteOrder(QDataStream::LittleEndian); + quint32 kind = 0; + qint8 format = 0; + in >> kind >> format; + + NodeInfo result; + + // we need only the string-type value + if (format == 'A') { + result.name = QString::fromLocal8Bit(name.constData()); + result.value = QString::fromLocal8Bit(value.constData()); + } + + return result; +} + +static QList enumerateDesiredNodes(const QVector &mib) +{ + QList nodes; + + QVector oid = mib; + + forever { + const QVector nextoid = nextOid(oid); + if (nextoid.isEmpty()) + break; + + const NodeInfo node = nodeForOid(nextoid); + if (!node.name.isEmpty()) { + if (node.name.endsWith("\%desc") + || node.name.endsWith("\%pnpinfo")) { + nodes.append(node); + } + } + + oid = nextoid; + } + + return nodes; +} + +QList availablePortsBySysctl(bool &ok) +{ + const QVector mib = mibFromName(QLatin1String("dev")); + if (mib.isEmpty()) { + ok = false; + return QList(); + } + + const QList nodes = enumerateDesiredNodes(mib); + if (nodes.isEmpty()) { + ok = false; + return QList(); + } + + QDir deviceDir(QLatin1String("/dev")); + if (!(deviceDir.exists() && deviceDir.isReadable())) { + ok = false; + return QList(); + } + + deviceDir.setNameFilters(QStringList() << QLatin1String("cua*")); + deviceDir.setFilter(QDir::Files | QDir::System | QDir::NoSymLinks); + + QList serialPortInfoList; + + foreach (const QString &portName, deviceDir.entryList()) { + if (portName.endsWith(QLatin1String(".init")) + || portName.endsWith(QLatin1String(".lock"))) { + continue; + } + + QSerialPortInfoPrivate priv; + priv.portName = portName; + priv.device = QSerialPortInfoPrivate::portNameToSystemLocation(portName); + + foreach (const NodeInfo &node, nodes) { + const int pnpinfoindex = node.name.indexOf(QLatin1String("\%pnpinfo")); + if (pnpinfoindex == -1) + continue; + + if (node.value.isEmpty()) + continue; + + QString ttyname = deviceName(node.value); + if (ttyname.isEmpty()) + continue; + + const QString ttyportscount = deviceCount(node.value); + if (ttyportscount.isEmpty()) + continue; + + const int count = ttyportscount.toInt(); + if (count == 0) + continue; + if (count > 1) { + bool matched = false; + for (int i = 0; i < count; ++i) { + const QString ends = QString(QLatin1String("%1.%2")).arg(ttyname).arg(i); + if (portName.endsWith(ends)) { + matched = true; + break; + } + } + + if (!matched) + continue; + } else { + if (!portName.endsWith(ttyname)) + continue; + } + + priv.serialNumber = deviceSerialNumber(node.value); + priv.vendorIdentifier = deviceVendorIdentifier(node.value, priv.hasVendorIdentifier); + priv.productIdentifier = deviceProductIdentifier(node.value, priv.hasProductIdentifier); + + const QString nodebase = node.name.mid(0, pnpinfoindex); + const QString descnode = QString(QLatin1String("%1\%desc")).arg(nodebase); + + // search for description and manufacturer properties + foreach (const NodeInfo &node, nodes) { + if (node.name != descnode) + continue; + + if (node.value.isEmpty()) + continue; + + // We can not separate the description and the manufacturer + // properties from the node value, so lets just duplicate it. + priv.description = deviceDescriptionAndManufacturer(node.value); + priv.manufacturer = priv.description; + break; + } + + break; + } + + serialPortInfoList.append(priv); + } + + ok = true; + return serialPortInfoList; +} + +#endif // Q_OS_FREEBSD + static bool isRfcommDevice(const QString &portName) { if (!portName.startsWith(QLatin1String("rfcomm"))) @@ -446,6 +729,11 @@ QList QSerialPortInfo::availablePorts() serialPortInfoList = availablePortsBySysfs(ok); #endif +#ifdef Q_OS_FREEBSD + if (!ok) + serialPortInfoList = availablePortsBySysctl(ok); +#endif + if (!ok) serialPortInfoList = availablePortsByFiltersOfDevices(ok); -- cgit v1.2.3