File indexing completed on 2024-05-19 05:29:59

0001 /*
0002  *   SPDX-FileCopyrightText: 2001 Matthias Hoelzer-Kluepfel <mhk@caldera.de>
0003  *   SPDX-License-Identifier: GPL-2.0-or-later
0004  */
0005 
0006 #include "usbdevices.h"
0007 
0008 #include <fcntl.h>
0009 #include <sys/stat.h>
0010 #include <sys/types.h>
0011 #include <unistd.h>
0012 
0013 #include <QDebug>
0014 #include <QFile>
0015 #include <QHash>
0016 
0017 #include <KLocalizedString>
0018 
0019 #include "usbdb.h"
0020 
0021 // FreeBSD has its own libusb implementation that does not provide
0022 // libusb_get_parent(), even if the advertised LIBUSB_API_VERSION
0023 // is the same as when libusb upstream introduced libusb_get_parent().
0024 #ifdef Q_OS_FREEBSD
0025 
0026 #define libusb_get_parent(device) nullptr
0027 
0028 #endif
0029 
0030 QList<USBDevice *> USBDevice::_devices;
0031 libusb_context *USBDevice::_context;
0032 USBDB *USBDevice::_db;
0033 
0034 static double convertLibusbSpeed(int speed)
0035 {
0036     switch (speed) {
0037     case LIBUSB_SPEED_UNKNOWN:
0038         // not using default here to catch future enum values
0039         return 0;
0040     case LIBUSB_SPEED_LOW:
0041         return 1.5;
0042     case LIBUSB_SPEED_FULL:
0043         return 12;
0044     case LIBUSB_SPEED_HIGH:
0045         return 480;
0046     case LIBUSB_SPEED_SUPER:
0047         return 5000;
0048 #if LIBUSB_API_VERSION >= 0x01000106
0049     case LIBUSB_SPEED_SUPER_PLUS:
0050         return 10000;
0051 #endif
0052     }
0053     return 0;
0054 };
0055 
0056 static void convertLibusbUsbVersion(uint16_t bcdUSB, unsigned int *verMajor, unsigned int *verMinor)
0057 {
0058     *verMajor = bcdUSB >> 8;
0059     *verMinor = ((bcdUSB & 0xf0) >> 4) * 10 + (bcdUSB & 0xf);
0060 }
0061 
0062 static QString prettyLibusbClassName(int class_code)
0063 {
0064     switch (class_code) {
0065     case LIBUSB_CLASS_PER_INTERFACE:
0066         return i18nc("USB device class", "(Defined at Interface level)");
0067     case LIBUSB_CLASS_AUDIO:
0068         return i18nc("USB device class", "Audio");
0069     case LIBUSB_CLASS_COMM:
0070         return i18nc("USB device class", "Communications");
0071     case LIBUSB_CLASS_HID:
0072         return i18nc("USB device class", "Human Interface Device");
0073     case LIBUSB_CLASS_PHYSICAL:
0074         return i18nc("USB device class", "Physical Interface Device");
0075     case LIBUSB_CLASS_PRINTER:
0076         return i18nc("USB device class", "Printer");
0077     case LIBUSB_CLASS_IMAGE:
0078         return i18nc("USB device class", "Imaging");
0079     case LIBUSB_CLASS_MASS_STORAGE:
0080         return i18nc("USB device class", "Mass Storage");
0081     case LIBUSB_CLASS_HUB:
0082         return i18nc("USB device class", "Hub");
0083     case LIBUSB_CLASS_DATA:
0084         return i18nc("USB device class", "CDC Data");
0085     case LIBUSB_CLASS_SMART_CARD:
0086         return i18nc("USB device class", "Chip/SmartCard");
0087     case LIBUSB_CLASS_CONTENT_SECURITY:
0088         return i18nc("USB device class", "Content Security");
0089     case LIBUSB_CLASS_VIDEO:
0090         return i18nc("USB device class", "Video");
0091     case LIBUSB_CLASS_PERSONAL_HEALTHCARE:
0092         return i18nc("USB device class", "Personal Healthcare");
0093     case LIBUSB_CLASS_DIAGNOSTIC_DEVICE:
0094         return i18nc("USB device class", "Diagnostic");
0095     case LIBUSB_CLASS_WIRELESS:
0096         return i18nc("USB device class", "Wireless");
0097 #if LIBUSB_API_VERSION >= 0x01000108
0098     case LIBUSB_CLASS_MISCELLANEOUS:
0099         return i18nc("USB device class", "Miscellaneous Device");
0100 #endif
0101     case LIBUSB_CLASS_APPLICATION:
0102         return i18nc("USB device class", "Application Specific Interface");
0103     case LIBUSB_CLASS_VENDOR_SPEC:
0104         return i18nc("USB device class", "Vendor Specific Class");
0105     }
0106     return QString();
0107 }
0108 
0109 USBDevice::USBDevice(libusb_device *dev, struct libusb_device_descriptor &dev_desc)
0110     : _bus(libusb_get_bus_number(dev))
0111     , _level(0)
0112     , _parent(0)
0113     , _port(libusb_get_port_number(dev))
0114     , _device(libusb_get_device_address(dev))
0115     , _channels(0)
0116     , _speed(convertLibusbSpeed(libusb_get_device_speed(dev)))
0117     , _verMajor(0)
0118     , _verMinor(0)
0119     , _class(dev_desc.bDeviceClass)
0120     , _sub(dev_desc.bDeviceSubClass)
0121     , _prot(dev_desc.bDeviceProtocol)
0122     , _maxPacketSize(dev_desc.bMaxPacketSize0)
0123     , _vendorID(dev_desc.idVendor)
0124     , _prodID(dev_desc.idProduct)
0125 {
0126     _devices.append(this);
0127 
0128     if (!_db)
0129         _db = new USBDB;
0130 
0131     convertLibusbUsbVersion(dev_desc.bcdUSB, &_verMajor, &_verMinor);
0132 
0133     collectDataSys(dev);
0134 }
0135 
0136 USBDevice::~USBDevice()
0137 {
0138 }
0139 
0140 #if defined(Q_OS_LINUX)
0141 
0142 static QString catFile(const QString &fname)
0143 {
0144     char buffer[256];
0145     QString result;
0146     int fd = ::open(QFile::encodeName(fname).constData(), O_RDONLY);
0147     if (fd < 0)
0148         return QString();
0149 
0150     if (fd >= 0) {
0151         ssize_t count;
0152         while ((count = ::read(fd, buffer, 256)) > 0)
0153             result.append(QString::fromLatin1(buffer, count));
0154 
0155         ::close(fd);
0156     }
0157     return result.trimmed();
0158 }
0159 
0160 static QString devpath(libusb_device *dev)
0161 {
0162     // hardcoded to 7 as the libusb apidocs says:
0163     // "As per the USB 3.0 specs, the current maximum limit for the depth is 7."
0164     static const int ports = 7;
0165     uint8_t port_numbers[ports];
0166     const int num = libusb_get_port_numbers(dev, port_numbers, ports);
0167     QString ret;
0168     for (uint8_t i = 0; i < num; ++i) {
0169         if (i > 0)
0170             ret += QLatin1Char('.');
0171         ret += QString::number(port_numbers[i]);
0172     }
0173     return ret;
0174 }
0175 
0176 void USBDevice::collectDataSys(libusb_device *dev)
0177 {
0178     const QString dname =
0179         _port == 0 ? QStringLiteral("/sys/bus/usb/devices/usb%1").arg(_bus) : QStringLiteral("/sys/bus/usb/devices/%1-%2").arg(_bus).arg(devpath(dev));
0180 
0181     _manufacturer = catFile(dname + QStringLiteral("/manufacturer"));
0182     _product = catFile(dname + QStringLiteral("/product"));
0183     if (_device == 1)
0184         _product += QStringLiteral(" (%1)").arg(_bus);
0185     _serial = catFile(dname + QStringLiteral("/serial"));
0186     _channels = catFile(dname + QStringLiteral("/maxchild")).toUInt();
0187 }
0188 
0189 #else
0190 
0191 void USBDevice::collectDataSys(libusb_device *dev)
0192 {
0193     Q_UNUSED(dev);
0194 }
0195 
0196 #endif
0197 
0198 USBDevice *USBDevice::find(int bus, int device)
0199 {
0200     for (USBDevice *usbDevice : std::as_const(_devices)) {
0201         if (usbDevice->bus() == bus && usbDevice->device() == device)
0202             return usbDevice;
0203     }
0204 
0205     return nullptr;
0206 }
0207 
0208 QString USBDevice::product()
0209 {
0210     if (!_product.isEmpty())
0211         return _product;
0212     QString pname = _db->device(_vendorID, _prodID);
0213     if (!pname.isEmpty())
0214         return pname;
0215     return i18n("Unknown");
0216 }
0217 
0218 QString USBDevice::dump()
0219 {
0220     QString r;
0221 
0222     r = QStringLiteral("<qml><h2><center>") + product() + QStringLiteral("</center></h2><br/><hl/>");
0223 
0224     if (!_manufacturer.isEmpty())
0225         r += i18n("<b>Manufacturer:</b> ") + _manufacturer + QStringLiteral("<br/>");
0226     if (!_serial.isEmpty())
0227         r += i18n("<b>Serial #:</b> ") + _serial + QStringLiteral("<br/>");
0228 
0229     r += QLatin1String("<br/><table>");
0230 
0231     QString c = QStringLiteral("<td>%1</td>").arg(_class);
0232     QString cname = prettyLibusbClassName(_class);
0233     if (cname.isEmpty()) {
0234         cname = _db->cls(_class);
0235         if (!cname.isEmpty())
0236             cname = i18nc("USB device class", cname.toUtf8().constData());
0237     }
0238     if (!cname.isEmpty())
0239         c += QStringLiteral("<td>(") + cname + QStringLiteral(")</td>");
0240     r += i18n("<tr><td><i>Class</i></td>%1</tr>", c);
0241     QString sc = QStringLiteral("<td>%1</td>").arg(_sub);
0242     QString scname = _db->subclass(_class, _sub);
0243     if (!scname.isEmpty())
0244         sc += QStringLiteral("<td>(") + i18nc("USB device subclass", scname.toLatin1().constData()) + QStringLiteral(")</td>");
0245     r += i18n("<tr><td><i>Subclass</i></td>%1</tr>", sc);
0246     QString pr = QStringLiteral("<td>%1</td>").arg(_prot);
0247     QString prname = _db->protocol(_class, _sub, _prot);
0248     if (!prname.isEmpty())
0249         pr += QStringLiteral("<td>(") + prname + QStringLiteral(")</td>");
0250     r += i18n("<tr><td><i>Protocol</i></td>%1</tr>", pr);
0251     r += ki18n("<tr><td><i>USB Version</i></td><td>%1.%2</td></tr>").subs(_verMajor).subs(_verMinor, 2, 10, QChar::fromLatin1('0')).toString();
0252     r += QLatin1String("<tr><td></td></tr>");
0253 
0254     QString v = QStringLiteral("%1").arg(_vendorID, 4, 16, QLatin1Char('0'));
0255     QString name = _db->vendor(_vendorID);
0256     if (!name.isEmpty())
0257         v += QStringLiteral("<td>(") + name + QStringLiteral(")</td>");
0258     r += i18n("<tr><td><i>Vendor ID</i></td><td>0x%1</td></tr>", v);
0259     QString p = QStringLiteral("%1").arg(_prodID, 4, 16, QLatin1Char('0'));
0260     QString pname = _db->device(_vendorID, _prodID);
0261     if (!pname.isEmpty())
0262         p += QStringLiteral("<td>(") + pname + QStringLiteral(")</td>");
0263     r += i18n("<tr><td><i>Product ID</i></td><td>0x%1</td></tr>", p);
0264     r += QLatin1String("<tr><td></td></tr>");
0265 
0266     r += i18n("<tr><td><i>Speed</i></td><td>%1 Mbit/s</td></tr>", _speed);
0267     r += i18n("<tr><td><i>Channels</i></td><td>%1</td></tr>", _channels);
0268     r += i18n("<tr><td><i>Max. Packet Size</i></td><td>%1</td></tr>", _maxPacketSize);
0269     r += QLatin1String("<tr><td></td></tr>");
0270 
0271     r += QLatin1String("</table>");
0272 
0273     return r;
0274 }
0275 
0276 bool USBDevice::load()
0277 {
0278     if (!_context) {
0279         const int r = libusb_init(&_context);
0280         if (r < 0) {
0281             qWarning() << "Failed to initialize libusb:" << r << libusb_error_name(r);
0282             return false;
0283         }
0284     }
0285 
0286     qDeleteAll(_devices);
0287     _devices.clear();
0288 
0289     libusb_device **devs;
0290     const ssize_t count = libusb_get_device_list(_context, &devs);
0291     if (count < 0) {
0292         qWarning() << "Cannot get the list of USB devices";
0293         return false;
0294     }
0295 
0296     QHash<libusb_device *, USBDevice *> devBylibusbMap;
0297     QHash<USBDevice *, libusb_device *> libusbByDevMap;
0298 
0299     for (ssize_t i = 0; i < count; ++i) {
0300         libusb_device *dev = devs[i];
0301         struct libusb_device_descriptor dev_desc;
0302         int r = libusb_get_device_descriptor(dev, &dev_desc);
0303         if (r < 0) {
0304             qWarning() << "libusb_get_device_descriptor failed:" << r << libusb_error_name(r);
0305             continue;
0306         }
0307         USBDevice *device = new USBDevice(dev, dev_desc);
0308         devBylibusbMap.insert(dev, device);
0309         libusbByDevMap.insert(device, dev);
0310     }
0311 
0312     auto levels = [](libusb_device *dev) {
0313         int level = 0;
0314         for (libusb_device *p = libusb_get_parent(dev); p; p = libusb_get_parent(p)) {
0315             ++level;
0316         }
0317         return level;
0318     };
0319     for (int i = 0; i < _devices.count(); ++i) {
0320         USBDevice *device = _devices[i];
0321         libusb_device *dev = libusbByDevMap.value(device);
0322         device->_level = levels(dev);
0323         libusb_device *parentDev = libusb_get_parent(dev);
0324         if (parentDev)
0325             device->_parent = devBylibusbMap.value(parentDev)->_device;
0326     }
0327 
0328     libusb_free_device_list(devs, 1);
0329 
0330     return true;
0331 }
0332 
0333 void USBDevice::clear()
0334 {
0335     qDeleteAll(_devices);
0336     _devices.clear();
0337 
0338     if (_context) {
0339         libusb_exit(_context);
0340         _context = nullptr;
0341     }
0342 }