Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,15 +1,184 @@
// SPDX-FileCopyrightText: 2019 ~ 2023 UnionTech Software Technology Co., Ltd.
// SPDX-FileCopyrightText: 2019 ~ 2026 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: GPL-3.0-or-later

#include "monitorusb.h"
#include "controlinterface.h"
#include "mainjob.h"

Check warning on line 7 in deepin-devicemanager-server/deepin-deviceinfo/src/hotplug/monitorusb.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: "mainjob.h" not found.

#include <QLoggingCategory>

Check warning on line 9 in deepin-devicemanager-server/deepin-deviceinfo/src/hotplug/monitorusb.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QLoggingCategory> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QProcess>
#include <QFile>

Check warning on line 10 in deepin-devicemanager-server/deepin-deviceinfo/src/hotplug/monitorusb.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QFile> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QDateTime>

Check warning on line 11 in deepin-devicemanager-server/deepin-deviceinfo/src/hotplug/monitorusb.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QDateTime> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QDir>

Check warning on line 12 in deepin-devicemanager-server/deepin-deviceinfo/src/hotplug/monitorusb.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QDir> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QStringList>

Check warning on line 13 in deepin-devicemanager-server/deepin-deviceinfo/src/hotplug/monitorusb.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QStringList> not found. Please note: Cppcheck does not need standard library headers to get proper results.

// ---------------------------------------------------------------------------
// Sysfs-based USB device enumeration
//
// Reading /sys/bus/usb/devices/ replaces the external "hwinfo --usb" process.
// hwinfo opens every /dev/bus/usb/*/* node and issues ioctl(USBDEVFS_CONTROL)
// control transfers to read descriptors; on some xHCI controllers these
// control transfers stall isochronous audio URBs for ~970 ms, exceeding the
// ~93 ms PulseAudio buffer and causing audible stuttering (PMS 381051).
// Reading sysfs attribute files performs no USB I/O, eliminating the
// control-transfer interference.
//
// The generated string keeps the key:value line format that
// EnableUtils::disableOutDevice() and WakeupUtils::updateWakeupDeviceInfo()
// already parse (lines split on ": ", device blocks separated by blank lines,
// >10 lines per block).
// ---------------------------------------------------------------------------

// Read a sysfs attribute file and return its trimmed content.
static QString readSysfsAttr(const QString &path)
{
QFile file(path);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
return QString();
return QString::fromLocal8Bit(file.readAll()).trimmed();
}

// Determine the hwinfo-style "Hardware Class" for a USB device.
// Only the values downstream consumers act on are distinguished:
// "hub" -- bDeviceClass 0x09 (filtered out by getMapInfo)
// "keyboard" -- HID boot interface, protocol 1
// "mouse" -- HID boot interface, protocol 2
// Everything else is reported as "usb".
static QString usbHardwareClass(const QString &deviceDir, const QString &deviceName)
{
const QString bDeviceClass = readSysfsAttr(deviceDir + QStringLiteral("/bDeviceClass"));
if (bDeviceClass == QLatin1String("09"))
return QStringLiteral("hub");

QDir dir(deviceDir);
const QStringList entries = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::Name);
for (const QString &entry : entries) {
if (!entry.startsWith(deviceName + QLatin1Char(':')))
continue;
const QString ifaceDir = deviceDir + QLatin1Char('/') + entry;
const QString bInterfaceClass = readSysfsAttr(ifaceDir + QStringLiteral("/bInterfaceClass"));
if (bInterfaceClass != QLatin1String("03")) // HID
continue;
const QString bInterfaceSubClass = readSysfsAttr(ifaceDir + QStringLiteral("/bInterfaceSubClass"));
const QString bInterfaceProtocol = readSysfsAttr(ifaceDir + QStringLiteral("/bInterfaceProtocol"));
if (bInterfaceSubClass == QLatin1String("01")) { // Boot interface
if (bInterfaceProtocol == QLatin1String("01"))
return QStringLiteral("keyboard");
if (bInterfaceProtocol == QLatin1String("02"))
return QStringLiteral("mouse");
}
}
return QStringLiteral("usb");
}

// Build a hwinfo --usb compatible information string by reading
// /sys/bus/usb/devices/ directly (no ioctl, no USB control transfers).
static QString getUsbInfoFromSysfs()
{
QDir baseDir(QStringLiteral("/sys/bus/usb/devices"));
const QStringList deviceEntries = baseDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::Name);

QStringList deviceBlocks;
int index = 0;

for (const QString &entry : deviceEntries) {
// Skip interface entries such as "1-3:1.0" -- only enumerate devices.
if (entry.contains(QLatin1Char(':')))
continue;

const QString deviceDir = QStringLiteral("/sys/bus/usb/devices/") + entry;
const QString idVendor = readSysfsAttr(deviceDir + QStringLiteral("/idVendor"));
const QString idProduct = readSysfsAttr(deviceDir + QStringLiteral("/idProduct"));
if (idVendor.isEmpty() || idProduct.isEmpty())
continue;

const QString manufacturer = readSysfsAttr(deviceDir + QStringLiteral("/manufacturer"));
const QString product = readSysfsAttr(deviceDir + QStringLiteral("/product"));
const QString serial = readSysfsAttr(deviceDir + QStringLiteral("/serial"));
const QString bcdDevice = readSysfsAttr(deviceDir + QStringLiteral("/bcdDevice"));
const QString bDeviceClass = readSysfsAttr(deviceDir + QStringLiteral("/bDeviceClass"));
const QString bDeviceSubClass = readSysfsAttr(deviceDir + QStringLiteral("/bDeviceSubClass"));
const QString bDeviceProtocol = readSysfsAttr(deviceDir + QStringLiteral("/bDeviceProtocol"));
const QString busnum = readSysfsAttr(deviceDir + QStringLiteral("/busnum"));
const QString devnum = readSysfsAttr(deviceDir + QStringLiteral("/devnum"));
const QString speed = readSysfsAttr(deviceDir + QStringLiteral("/speed"));

// Canonical sysfs path, stripped of the /sys prefix so it matches
// hwinfo's "SysFS Device Link" (e.g. /devices/pci.../usb1/1-3).
const QString canonicalDevice = QDir(deviceDir).canonicalPath();
if (canonicalDevice.isEmpty())
continue;
QString sysfsDeviceLink = canonicalDevice;
if (sysfsDeviceLink.startsWith(QStringLiteral("/sys")))
sysfsDeviceLink = sysfsDeviceLink.mid(4);

// SysFS ID uses the first interface path, falling back to the device
// path when no interface exists.
QString sysfsId = sysfsDeviceLink;
QDir devDir(deviceDir);
const QStringList subEntries = devDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::Name);
for (const QString &sub : subEntries) {
if (!sub.startsWith(entry + QLatin1Char(':')))
continue;
const QString ifaceCanonical = QDir(deviceDir + QLatin1Char('/') + sub).canonicalPath();
if (!ifaceCanonical.isEmpty()) {
sysfsId = ifaceCanonical;
if (sysfsId.startsWith(QStringLiteral("/sys")))
sysfsId = sysfsId.mid(4);
break;
}
}

const QString hwClass = usbHardwareClass(deviceDir, entry);

// Detect USB network adapters so disableOutDevice() can obtain the
// interface name ("Device File") and MAC ("Permanent HW Address").
QString permHwAddr;
QString deviceFile;
for (const QString &sub : subEntries) {
if (!sub.startsWith(entry + QLatin1Char(':')))
continue;
QDir netDir(deviceDir + QLatin1Char('/') + sub + QStringLiteral("/net"));
if (netDir.exists()) {
const QStringList netEntries = netDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
if (!netEntries.isEmpty()) {
deviceFile = netEntries.first();
permHwAddr = readSysfsAttr(netDir.absoluteFilePath(deviceFile) + QStringLiteral("/address"));
break;
}
}
}

const QString moduleAlias = QStringLiteral("usb:v%1p%2d%3dc%4dsc%5dp%6")
.arg(idVendor).arg(idProduct).arg(bcdDevice)
.arg(bDeviceClass).arg(bDeviceSubClass).arg(bDeviceProtocol);

QStringList lines;
lines << QStringLiteral("%1: USB 00.0: 0000 USB Device").arg(++index);
lines << QStringLiteral(" [Created at sysfs]");
lines << QStringLiteral(" Unique ID: %1").arg(entry);
lines << QStringLiteral(" SysFS ID: %1").arg(sysfsId);
lines << QStringLiteral(" SysFS BusID: usb%1:%2").arg(busnum).arg(devnum);
lines << QStringLiteral(" SysFS Device Link: %1").arg(sysfsDeviceLink);
lines << QStringLiteral(" Hardware Class: %1").arg(hwClass);
lines << QStringLiteral(" Model: \"%1\"").arg(product);
lines << QStringLiteral(" Vendor: usb 0x%1 \"%2\"").arg(idVendor).arg(manufacturer);
lines << QStringLiteral(" Device: usb 0x%1 \"%2\"").arg(idProduct).arg(product);
lines << QStringLiteral(" Revision: \"%1\"").arg(bcdDevice);
if (!serial.isEmpty())
lines << QStringLiteral(" Serial ID: \"%1\"").arg(serial);
if (!permHwAddr.isEmpty())
lines << QStringLiteral(" Permanent HW Address: %1").arg(permHwAddr);
if (!deviceFile.isEmpty())
lines << QStringLiteral(" Device File: %1").arg(deviceFile);
lines << QStringLiteral(" Speed: %1 Mb/s").arg(speed);
lines << QStringLiteral(" Module Alias: \"%1\"").arg(moduleAlias);

deviceBlocks << lines.join(QLatin1Char('\n'));
}

return deviceBlocks.join(QStringLiteral("\n\n"));
}

MonitorUsb::MonitorUsb()
: m_Udev(nullptr)
Expand Down Expand Up @@ -78,10 +247,12 @@
// 只有add和remove事件才会更新缓存信息
strcpy(buf, udev_device_get_action(dev));
if ((0 == strcmp("add", buf) || 0 == strcmp("remove", buf)) && m_workingFlag) {
QProcess process;
process.start("hwinfo --usb");
process.waitForFinished(-1);
QString info = process.readAllStandardOutput();
// Read USB device info from sysfs instead of spawning "hwinfo --usb".
// hwinfo issues ioctl(USBDEVFS_CONTROL) control transfers to every
// USB device; on affected xHCI controllers these stall audio
// isochronous URBs and cause playback stuttering (PMS 381051).
// Sysfs reads perform no USB I/O, eliminating the interference.
QString info = getUsbInfoFromSysfs();
if (0 == strcmp("add", buf)) {
ControlInterface::getInstance()->disableOutDevice(info);
}
Expand Down
Loading