1 /*
2  *   SPDX-FileCopyrightText: 2001 Matthias Hoelzer-Kluepfel <mhk@caldera.de>
3  *   SPDX-License-Identifier: GPL-2.0-or-later
4  */
5 
6 #include "usbdevices.h"
7 
8 #include <fcntl.h>
9 #include <sys/stat.h>
10 #include <sys/types.h>
11 #include <unistd.h>
12 
13 #include <QDebug>
14 #include <QFile>
15 #include <QHash>
16 
17 #include <KLocalizedString>
18 
19 #include "usbdb.h"
20 
21 // FreeBSD has its own libusb implementation that does not provide
22 // libusb_get_parent(), even if the advertised LIBUSB_API_VERSION
23 // is the same as when libusb upstream introduced libusb_get_parent().
24 #ifdef Q_OS_FREEBSD
25 
26 #define libusb_get_parent(device) nullptr
27 
28 #endif
29 
30 QList<USBDevice *> USBDevice::_devices;
31 libusb_context *USBDevice::_context;
32 USBDB *USBDevice::_db;
33 
convertLibusbSpeed(int speed)34 static double convertLibusbSpeed(int speed)
35 {
36     switch (speed) {
37     case LIBUSB_SPEED_UNKNOWN:
38         // not using default here to catch future enum values
39         return 0;
40     case LIBUSB_SPEED_LOW:
41         return 1.5;
42     case LIBUSB_SPEED_FULL:
43         return 12;
44     case LIBUSB_SPEED_HIGH:
45         return 480;
46     case LIBUSB_SPEED_SUPER:
47         return 5000;
48 #if LIBUSB_API_VERSION >= 0x01000106
49     case LIBUSB_SPEED_SUPER_PLUS:
50         return 10000;
51 #endif
52     }
53     return 0;
54 };
55 
convertLibusbUsbVersion(uint16_t bcdUSB,unsigned int * verMajor,unsigned int * verMinor)56 static void convertLibusbUsbVersion(uint16_t bcdUSB, unsigned int *verMajor, unsigned int *verMinor)
57 {
58     *verMajor = bcdUSB >> 8;
59     *verMinor = ((bcdUSB & 0xf0) >> 4) * 10 + (bcdUSB & 0xf);
60 }
61 
prettyLibusbClassName(int class_code)62 static QString prettyLibusbClassName(int class_code)
63 {
64     switch (class_code) {
65     case LIBUSB_CLASS_PER_INTERFACE:
66         return i18nc("USB device class", "(Defined at Interface level)");
67     case LIBUSB_CLASS_AUDIO:
68         return i18nc("USB device class", "Audio");
69     case LIBUSB_CLASS_COMM:
70         return i18nc("USB device class", "Communications");
71     case LIBUSB_CLASS_HID:
72         return i18nc("USB device class", "Human Interface Device");
73 #ifndef __DragonFly__
74     case LIBUSB_CLASS_PHYSICAL:
75         return i18nc("USB device class", "Physical Interface Device");
76 #endif
77     case LIBUSB_CLASS_PRINTER:
78         return i18nc("USB device class", "Printer");
79     case LIBUSB_CLASS_IMAGE:
80         return i18nc("USB device class", "Imaging");
81     case LIBUSB_CLASS_MASS_STORAGE:
82         return i18nc("USB device class", "Mass Storage");
83     case LIBUSB_CLASS_HUB:
84         return i18nc("USB device class", "Hub");
85     case LIBUSB_CLASS_DATA:
86         return i18nc("USB device class", "CDC Data");
87     case LIBUSB_CLASS_SMART_CARD:
88         return i18nc("USB device class", "Chip/SmartCard");
89     case LIBUSB_CLASS_CONTENT_SECURITY:
90         return i18nc("USB device class", "Content Security");
91     case LIBUSB_CLASS_VIDEO:
92         return i18nc("USB device class", "Video");
93     case LIBUSB_CLASS_PERSONAL_HEALTHCARE:
94         return i18nc("USB device class", "Personal Healthcare");
95     case LIBUSB_CLASS_DIAGNOSTIC_DEVICE:
96         return i18nc("USB device class", "Diagnostic");
97     case LIBUSB_CLASS_WIRELESS:
98         return i18nc("USB device class", "Wireless");
99 #if LIBUSB_API_VERSION >= 0x01000108
100     case LIBUSB_CLASS_MISCELLANEOUS:
101         return i18nc("USB device class", "Miscellaneous Device");
102 #endif
103     case LIBUSB_CLASS_APPLICATION:
104         return i18nc("USB device class", "Application Specific Interface");
105     case LIBUSB_CLASS_VENDOR_SPEC:
106         return i18nc("USB device class", "Vendor Specific Class");
107     }
108     return QString();
109 }
110 
USBDevice(libusb_device * dev,struct libusb_device_descriptor & dev_desc)111 USBDevice::USBDevice(libusb_device *dev, struct libusb_device_descriptor &dev_desc)
112     : _bus(libusb_get_bus_number(dev))
113     , _level(0)
114     , _parent(0)
115 #ifdef __DragonFly__
116     , _port(0) // libusb_get_port_number() is not exposed in libusb, but here only needed of Q_OS_LINUX
117 #else
118     , _port(libusb_get_port_number(dev))
119 #endif
120     , _device(libusb_get_device_address(dev))
121     , _channels(0)
122     , _speed(convertLibusbSpeed(libusb_get_device_speed(dev)))
123     , _verMajor(0)
124     , _verMinor(0)
125     , _class(dev_desc.bDeviceClass)
126     , _sub(dev_desc.bDeviceSubClass)
127     , _prot(dev_desc.bDeviceProtocol)
128     , _maxPacketSize(dev_desc.bMaxPacketSize0)
129     , _vendorID(dev_desc.idVendor)
130     , _prodID(dev_desc.idProduct)
131 {
132     _devices.append(this);
133 
134     if (!_db)
135         _db = new USBDB;
136 
137     convertLibusbUsbVersion(dev_desc.bcdUSB, &_verMajor, &_verMinor);
138 
139     collectDataSys(dev);
140 }
141 
~USBDevice()142 USBDevice::~USBDevice()
143 {
144 }
145 
146 #if defined(Q_OS_LINUX)
147 
catFile(const QString & fname)148 static QString catFile(const QString &fname)
149 {
150     char buffer[256];
151     QString result;
152     int fd = ::open(QFile::encodeName(fname).constData(), O_RDONLY);
153     if (fd < 0)
154         return QString();
155 
156     if (fd >= 0) {
157         ssize_t count;
158         while ((count = ::read(fd, buffer, 256)) > 0)
159             result.append(QString::fromLatin1(buffer, count));
160 
161         ::close(fd);
162     }
163     return result.trimmed();
164 }
165 
devpath(libusb_device * dev)166 static QString devpath(libusb_device *dev)
167 {
168     // hardcoded to 7 as the libusb apidocs says:
169     // "As per the USB 3.0 specs, the current maximum limit for the depth is 7."
170     static const int ports = 7;
171     uint8_t port_numbers[ports];
172     const int num = libusb_get_port_numbers(dev, port_numbers, ports);
173     QString ret;
174     for (uint8_t i = 0; i < num; ++i) {
175         if (i > 0)
176             ret += QLatin1Char('.');
177         ret += QString::number(port_numbers[i]);
178     }
179     return ret;
180 }
181 
collectDataSys(libusb_device * dev)182 void USBDevice::collectDataSys(libusb_device *dev)
183 {
184     const QString dname =
185         _port == 0 ? QStringLiteral("/sys/bus/usb/devices/usb%1").arg(_bus) : QStringLiteral("/sys/bus/usb/devices/%1-%2").arg(_bus).arg(devpath(dev));
186 
187     _manufacturer = catFile(dname + QStringLiteral("/manufacturer"));
188     _product = catFile(dname + QStringLiteral("/product"));
189     if (_device == 1)
190         _product += QStringLiteral(" (%1)").arg(_bus);
191     _serial = catFile(dname + QStringLiteral("/serial"));
192     _channels = catFile(dname + QStringLiteral("/maxchild")).toUInt();
193 }
194 
195 #else
196 
collectDataSys(libusb_device * dev)197 void USBDevice::collectDataSys(libusb_device *dev)
198 {
199     Q_UNUSED(dev);
200 }
201 
202 #endif
203 
find(int bus,int device)204 USBDevice *USBDevice::find(int bus, int device)
205 {
206     foreach (USBDevice *usbDevice, _devices) {
207         if (usbDevice->bus() == bus && usbDevice->device() == device)
208             return usbDevice;
209     }
210 
211     return nullptr;
212 }
213 
product()214 QString USBDevice::product()
215 {
216     if (!_product.isEmpty())
217         return _product;
218     QString pname = _db->device(_vendorID, _prodID);
219     if (!pname.isEmpty())
220         return pname;
221     return i18n("Unknown");
222 }
223 
dump()224 QString USBDevice::dump()
225 {
226     QString r;
227 
228     r = QStringLiteral("<qml><h2><center>") + product() + QStringLiteral("</center></h2><br/><hl/>");
229 
230     if (!_manufacturer.isEmpty())
231         r += i18n("<b>Manufacturer:</b> ") + _manufacturer + QStringLiteral("<br/>");
232     if (!_serial.isEmpty())
233         r += i18n("<b>Serial #:</b> ") + _serial + QStringLiteral("<br/>");
234 
235     r += QLatin1String("<br/><table>");
236 
237     QString c = QStringLiteral("<td>%1</td>").arg(_class);
238     QString cname = prettyLibusbClassName(_class);
239     if (cname.isEmpty()) {
240         cname = _db->cls(_class);
241         if (!cname.isEmpty())
242             cname = i18nc("USB device class", cname.toUtf8().constData());
243     }
244     if (!cname.isEmpty())
245         c += QStringLiteral("<td>(") + cname + QStringLiteral(")</td>");
246     r += i18n("<tr><td><i>Class</i></td>%1</tr>", c);
247     QString sc = QStringLiteral("<td>%1</td>").arg(_sub);
248     QString scname = _db->subclass(_class, _sub);
249     if (!scname.isEmpty())
250         sc += QStringLiteral("<td>(") + i18nc("USB device subclass", scname.toLatin1().constData()) + QStringLiteral(")</td>");
251     r += i18n("<tr><td><i>Subclass</i></td>%1</tr>", sc);
252     QString pr = QStringLiteral("<td>%1</td>").arg(_prot);
253     QString prname = _db->protocol(_class, _sub, _prot);
254     if (!prname.isEmpty())
255         pr += QStringLiteral("<td>(") + prname + QStringLiteral(")</td>");
256     r += i18n("<tr><td><i>Protocol</i></td>%1</tr>", pr);
257     r += ki18n("<tr><td><i>USB Version</i></td><td>%1.%2</td></tr>").subs(_verMajor).subs(_verMinor, 2, 10, QChar::fromLatin1('0')).toString();
258     r += QLatin1String("<tr><td></td></tr>");
259 
260     QString v = QString::number(_vendorID, 16);
261     QString name = _db->vendor(_vendorID);
262     if (!name.isEmpty())
263         v += QStringLiteral("<td>(") + name + QStringLiteral(")</td>");
264     r += i18n("<tr><td><i>Vendor ID</i></td><td>0x%1</td></tr>", v);
265     QString p = QString::number(_prodID, 16);
266     QString pname = _db->device(_vendorID, _prodID);
267     if (!pname.isEmpty())
268         p += QStringLiteral("<td>(") + pname + QStringLiteral(")</td>");
269     r += i18n("<tr><td><i>Product ID</i></td><td>0x%1</td></tr>", p);
270     r += QLatin1String("<tr><td></td></tr>");
271 
272     r += i18n("<tr><td><i>Speed</i></td><td>%1 Mbit/s</td></tr>", _speed);
273     r += i18n("<tr><td><i>Channels</i></td><td>%1</td></tr>", _channels);
274     r += i18n("<tr><td><i>Max. Packet Size</i></td><td>%1</td></tr>", _maxPacketSize);
275     r += QLatin1String("<tr><td></td></tr>");
276 
277     r += QLatin1String("</table>");
278 
279     return r;
280 }
281 
load()282 bool USBDevice::load()
283 {
284     if (!_context) {
285         const int r = libusb_init(&_context);
286         if (r < 0) {
287             qWarning() << "Failed to initialize libusb:" << r << libusb_error_name(r);
288             return false;
289         }
290     }
291 
292     qDeleteAll(_devices);
293     _devices.clear();
294 
295     libusb_device **devs;
296     const ssize_t count = libusb_get_device_list(_context, &devs);
297     if (count < 0) {
298         qWarning() << "Cannot get the list of USB devices";
299         return false;
300     }
301 
302     QHash<libusb_device *, USBDevice *> devBylibusbMap;
303     QHash<USBDevice *, libusb_device *> libusbByDevMap;
304 
305     for (ssize_t i = 0; i < count; ++i) {
306         libusb_device *dev = devs[i];
307         struct libusb_device_descriptor dev_desc;
308         int r = libusb_get_device_descriptor(dev, &dev_desc);
309         if (r < 0) {
310             qWarning() << "libusb_get_device_descriptor failed:" << r << libusb_error_name(r);
311             continue;
312         }
313         USBDevice *device = new USBDevice(dev, dev_desc);
314         devBylibusbMap.insert(dev, device);
315         libusbByDevMap.insert(device, dev);
316     }
317 
318     auto levels = [](libusb_device *dev) {
319         int level = 0;
320         for (libusb_device *p = libusb_get_parent(dev); p; p = libusb_get_parent(p)) {
321             ++level;
322         }
323         return level;
324     };
325     for (int i = 0; i < _devices.count(); ++i) {
326         USBDevice *device = _devices[i];
327         libusb_device *dev = libusbByDevMap.value(device);
328         device->_level = levels(dev);
329         libusb_device *parentDev = libusb_get_parent(dev);
330         if (parentDev)
331             device->_parent = devBylibusbMap.value(parentDev)->_device;
332     }
333 
334     libusb_free_device_list(devs, 1);
335 
336     return true;
337 }
338 
clear()339 void USBDevice::clear()
340 {
341     qDeleteAll(_devices);
342     _devices.clear();
343 
344     if (_context) {
345         libusb_exit(_context);
346         _context = nullptr;
347     }
348 }
349