File indexing completed on 2024-05-19 16:31: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 foreach (USBDevice *usbDevice, _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 }