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