介绍
在完美世界中, USB 设备和 hubs 表现良好,并设法解决他们遇到的任何问题。现实中会发生各种奇怪的事情,需要做点什么。常见的解决办法是拔掉 USB 插头,重新连接设备。但是,如果这需要自动发生怎么办?
最引人注目的解决方案出现在另一个网页上: 它会导致 USB hub的驱动程序关闭并重新启动。效果几乎等同于断开并重新连接计算机上的所有 USB 设备。那是最重的锤子,有时是必要的。
本页介绍了两种重置特定 USB 设备的方法。我将首先介绍如何执行每种方法,并提供一些简单的解释。在此之后,我将介绍很多技术细节。
此处的整个讨论仅限于 Linux ,尽管此处介绍的第二种方法可能也适用于其他平台。
这里的很多信息都取自 Universal Serial Bus Specification Revision 2.0。此文档的文件名通常为 usb_20.pdf。
方法#1: 要求 Linux kernel 重置设备
有几种工具可用于执行此操作。最先进的工具是 usbutils的一部分。下载 usbreset.c 并将 gcc 用于其编译(compilation):
$ gcc -O3 -Wall -g usbreset.c -o usbreset
进而:
$ ./usbreset Usage: usbreset PPPP:VVVV - reset by product and vendor id usbreset BBB/DDD - reset by bus and device number usbreset "Product" - reset by product name Devices: Number 001/004 ID 045e:07b2 Microsoft® Nano Transceiver v1.0 Number 001/002 ID 04f3:0103 $ ./usbreset 001/004 Resetting Microsoft® Nano Transceiver v1.0 ... can't open [Permission denied] $ sudo ./usbreset 001/004 Resetting Microsoft® Nano Transceiver v1.0 ... ok $ sudo ./usbreset 045e:07b2 Resetting Microsoft® Nano Transceiver v1.0 ... ok
作为对重置的响应, kernel log中出现以下内容:
usb 1-6: reset full-speed USB device number 4 using xhci_hcd
本次会议展示的内容:
- 没有任何 arguments, usbreset 以 USB 设备列表响应。使用 lsusb可以获得相同的信息。
- 您必须是 root 才能实际重置设备(因此 sudo)。
- 该设备可以通过其总线地址(bus address)(总线 number (bus number)和 device number)来选择。请注意,每次连接设备时总线地址都会更改。
- 该设备也可以通过其 Vendor / Product IDs选择。当只有一个同类的 USB 设备时,这是首选方法。
- 该设备未更改其在 USB 总线上的地址(它未因重置而重新枚举)。
那么 usbreset 是做什么的呢?它归结为 USB 设备的设备文件(device file)上的这个命令:
ioctl(fd, USBDEVFS_RESET, 0)
最后调用 kernel的 usb_reset_device()函数,它的作用远不止重置设备。这个想法是让整个过程顺利: 如果需要,此函数会通知设备的驱动程序前后重置。它在重置前解除绑定驱动程序,然后再绑定回去。设备的 configuration 也被重置后加载。没有这个,设备就不会知道它的总线地址,也不会准备好进行任何数据交换。
所以这几乎就像重新连接设备一样,但没有要求它识别自己(因为信息已经知道)并且没有为它分配新地址。
对于 USB 3.x,这是效率较低的 hot 复位。下面是关于 hot 复位的更多信息。
方法#2: 切换 USB 端口的 power 状态
也有多种工具可用于此目的。我将用 hubpower来演示这个方法。下载hubpower.c并执行编译:
$ gcc -O3 -Wall -g hubpower.c -o hubpower
使用此工具有点棘手,因为命令不会发送到 USB 设备本身。相反,请求被发送到 USB 设备连接到的 USB hub 。
所以第一步是弄清楚这是哪 hub 。首先,让我们找到 USB 设备的地址:
$ lsusb Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub Bus 001 Device 004: ID 045e:07b2 Microsoft Corp. Bus 001 Device 002: ID 04f3:0103 Elan Microelectronics Corp. ActiveJet K-2024 Multimedia Keyboard Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
所以设备在总线 number 1 (bus number 1)和 device number 4上。请注意,每次枚举 USB 设备时, device number 都会发生变化。
那么 USB 设备连接的是哪 hub 呢?到哪个端口?
$ lsusb -t /: Bus 02.Port 1: Dev 1, Class=root_hub, Driver=xhci_hcd/6p, 5000M /: Bus 01.Port 1: Dev 1, Class=root_hub, Driver=xhci_hcd/12p, 480M |__ Port 6: Dev 4, If 0, Class=Human Interface Device, Driver=usbhid, 12M |__ Port 6: Dev 4, If 1, Class=Human Interface Device, Driver=usbhid, 12M |__ Port 6: Dev 4, If 2, Class=Human Interface Device, Driver=usbhid, 12M |__ Port 11: Dev 2, If 0, Class=Human Interface Device, Driver=usbhid, 1.5M |__ Port 11: Dev 2, If 1, Class=Human Interface Device, Driver=usbhid, 1.5M
所以设备号 4 连接到端口 #6(port #6)。 hub的总线 number 是 1,它的 device number 是 1(它是 motherboard的 USB 控制器,起到 root hub的作用)。
让我们看看 hubpower 对此有何评论:
$ sudo ./hubpower 1:1 status
Port 1 status: 0100 Power-On
Port 2 status: 0100 Power-On
Port 3 status: 0100 Power-On
Port 4 status: 0100 Power-On
Port 5 status: 0100 Power-On
Port 6 status: 0103 Power-On Enabled Connected
Port 7 status: 0100 Power-On
Port 8 status: 0100 Power-On
Port 9 status: 0100 Power-On
Port 10 status: 0100 Power-On
Port 11 status: 0303 Low-Speed Power-On Enabled Connected
Port 12 status: 0100 Power-On
注意“1:1”部分是 hub的总线地址。如果这是一个外接的 hub,那么每次 hub 连接到电脑时,这个地址(address)都会发生变化。
但是端口的数字永远不会改变(只要设备连接到同一个物理端口)。
现在,当我们知道设备的端口 number(port number)时,让我们重置它:
$ sudo ./hubpower 1:1 power 6 off Port 6 status: 0000 Power-Off $ sudo ./hubpower 1:1 power 6 on Port 6 status: 0100 Power-On
在第一个命令后,设备将在 kernel log中报告为断开连接:
usb 1-6: USB disconnect, device number 4
在第二个命令之后,计算机的行为就像设备已物理连接一样:
usb 1-6: new full-speed USB device number 5 using xhci_hcd
usb 1-6: New USB device found, idVendor=045e, idProduct=07b2, bcd Device= 7.04
usb 1-6: New USB device strings: Mfr=1, Product=2, SerialNumber=0
usb 1-6: Product: Microsoft® Nano Transceiver v1.0
usb 1-6: Manufacturer: Microsoft
[ ... ]
因为这 re-enumeration,给设备分配了一个新的总线地址:
$ lsusb
Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 001 Device 005: ID 045e:07b2 Microsoft Corp.
Bus 001 Device 002: ID 04f3:0103 Elan Microelectronics Corp. ActiveJet K-2024 Multimedia Keyboard
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
那么这就像物理断开 USB 设备并重新连接一样吗?答案或许是: 在大多数情况下, hubpower 命令不会真正关闭 USB 设备的电源。因此,该设备将继续接收其 VBUS 电压(5V)。很少有 USB hubs 真正关掉电源。更多关于下面的内容。
对于绝大多数 hubs,第一个命令(“关闭电源”)只会将端口置于使其忽略设备的状态。第二个命令(“打开电源”)将端口返回到其自然状态。结果是检测到设备,并开始其初始化过程。其中,设备被重置和枚举,其驱动程序对其进行初始化。
因此,物理断开连接通常比这些命令更有效,尤其是当设备从 USB plug获得电源时。但如果 hub 真的关闭了 VBUS, hubpower 同样有效。
请注意,在此示例中,命令被发送到 motherboard的 USB 控制器( root hub)。原因是 USB 设备直接连接到计算机。如果此设备通过外部 hub连接到计算机,则应将命令发送到此外部 hub。在这两种情况下, hubpower 的使用方式相同。
不幸的是,hubpower 不支持 USB 3.0 (SuperSpeed)。更多关于下面的内容。
两种方法的区别
那么第二种方法(使用 hubpower)与第一种方法(使用 usbreset)有何不同?为了解决设备问题,这两种方法本质上是相同的: 他们发送重置命令。发生这种情况的方式截然不同,但仍然是这个重置命令可能会解决问题(如果有的话)。
但是有一些重要的区别:
- 即使在总线上无法识别设备,hubpower 也能正常工作。例如,如果计算机多次尝试枚举设备然后放弃。在这种情况下,设备可以物理连接到计算机,但 usbreset 没有用,因为设备没有总线地址。
- 当 USB 端口将被禁用时,只有 hubpower 可以提供帮助: 有时,在端口发生几次错误后,计算机会完全忽略 USB 端口。 hubpower 应该可以解决这个问题。
- hubpower 不适用于 SuperSpeed (USB 3.x)。这可能是增加支持的问题。请参阅下面有关 SuperSpeed 的更多信息。
- hubpower 也可以控制 USB 设备的 power supply (但通常不会发生这种情况)。如果设备需要电源回收来解决问题,这是一个优势。
- hubpower 更难使用: 需要找到 hub的端口中的哪一个连接到 USB 设备上。还有 hub的总线地址。
控制电气设备(?)
尽管本页的主题是如何解决 USB 设备的问题,但也有一个有趣的副作用: 有时可以控制 USB 端口的 5V power supply。换句话说,可以使用简单且便宜的 USB hub 打开和关闭能够处理 2.5 Watts的 power supply 。
这足以控制 electromechanical relay。所以只需要添加一个组件就可以控制一个运行在 110V / 220V上的电器。嗯,添加一个简单的二极管也是个好主意,以保护 USB hub 免受损坏。但仅此而已。
不幸的是,控制 power supply 的能力是可选的: 根据 USB 2.0 规范中的第 11.11 节,当端口处于 Powered-Off 状态时, hub 可能具有将 5V power supply 关闭到端口的 power switches 。或者,一 power switch 可用于控制多个端口(“ganged power switching”)的电源。控制电源的目的主要是为了关闭电流过大的 USB 设备,让剩余的端口继续正常工作。
但如前所述,物理电源控制是可选的。 hubpower 实际上所做的是更改端口的 PORT_POWER 属性的值(在 USB specifications中称为“feature”)。此更改涉及 hub的端口的两个不同方面:
- 强制的: 端口的逻辑状态。当 PORT_POWER 为零时,端口只能处于 Powered-Off 状态(或非 Configured(Not Configured))。换句话说, hub 必须表现得好像端口上没有电压,因此忽略任何连接的设备(如果存在)。
- 选修的: 端口的电源线上存在 VBUS ( 5V 电压)。如果 hub 不支持此功能,即使 PORT_POWER 为零,电压也可能存在。
下面对PORT_POWER 进行了更详细的解释。
我的 hub 是否控制电压?
您如何知道您的 hub 是否真的关闭了电压?唯一可以确定的方法是对其进行测试。连接任何不是 USB 设备但会消耗 USB 端口电源的设备。真正的 USB 设备可能会增加混乱。例如,光电鼠标通常会响应关机命令关闭其 LED ,即使 5V 电压仍然存在。
每 hub 在“lsusb -v”可见的信息(在 Hub Descriptor中,在规范的第 11.23.2.1 节中定义)中声明它是否控制电压,以及控制到什么粒度。 hub 可能会声明它不控制电压,或者它控制端口(“gangs”)组中的电压,或者它单独控制每个端口的电压: 根据 USB 2.0 规范中的第 11.11 节,“a hub with power switches can switch power to all ports as a group/gang, to each port individually, or have an arbitrary number of gangs of one or more 端口”。
不过,这个消息并不可靠。我遇到过几 hubs 声称他们控制电压,但他们都没有。
例如,这是“lsusb -v”的输出
Bus 001 Device 073: ID 0bda:5411 Realtek Semiconductor Corp. Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 2.10 bDeviceClass 9 Hub bDeviceSubClass 0 Unused bDeviceProtocol 2 TT per port bMaxPacketSize0 64 idVendor 0x0bda Realtek Semiconductor Corp. idProduct 0x5411 bcdDevice 1.23 iManufacturer 1 Generic iProduct 2 4-Port USB 2.0 Hub iSerial 0 bNumConfigurations 1 Configuration Descriptor: [ ... ] Hub Descriptor: bLength 9 bDescriptorType 41 nNbrPorts 4 wHubCharacteristic 0x00a9 Per-port power switching Per-port overcurrent protection TT think time 16 FS bits Port indicators bPwrOn2PwrGood 0 * 2 milli seconds bHubContrCurrent 100 milli Ampere DeviceRemovable 0x00 PortPwrCtrlMask 0xff Hub Port Status: Port 1: 0000.0503 highspeed power enable connect Port 2: 0000.0503 highspeed power enable connect Port 3: 0000.0100 power Port 4: 0000.0100 power
看起来很乐观,不是吗?实际上,这 hub 根本不控制电压。
相比之下,这是一块普通主板的 hub:
Bus 005 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 2.00 bDeviceClass 9 Hub bDeviceSubClass 0 Unused bDeviceProtocol 1 Single TT bMaxPacketSize0 64 idVendor 0x1d6b Linux Foundation idProduct 0x0002 2.0 root hub bcdDevice 4.15 iManufacturer 3 Linux 4.15.0-20-generic xhci-hcd iProduct 2 xHCI Host Controller iSerial 1 0000:06:00.0 bNumConfigurations 1 Configuration Descriptor: [ ... ] Hub Descriptor: bLength 9 bDescriptorType 41 nNbrPorts 2 wHubCharacteristic 0x000a No power switching (usb 1.0) Per-port overcurrent protection TT think time 8 FS bits bPwrOn2PwrGood 10 * 2 milli seconds bHubContrCurrent 0 milli Ampere DeviceRemovable 0x00 PortPwrCtrlMask 0xff Hub Port Status: Port 1: 0000.0100 power Port 2: 0000.0100 power Device Status: 0x0001 Self Powered
所以这款 hub 承认不支持任何电压控制。
请注意,除了有关电源开关的信息外,还有有关每个端口的状态信息,如“Hub Port Status”。这些 status 比特在 USB 2.0 规范(第 11.24.2.7.1 节)的表 11-21 中定义。这与 hubpower获取的信息相同。
uhubctl 工具
用 USB hub 控制 relay 的想法是许多计划的动机。 uhubctl值得一看,主要是因为该项目维护了一个控制电压的 USB hubs 列表。
该工具显然仅用于电压控制,而不用于解决 USB 设备的问题。例如,默认情况下, uhubctl 会忽略未声明能够为每个端口单独控制电压的 hubs 。
这些命令等同于上面的 hubpower 命令:
$ sudo ./uhubctl -f -l 1 -p 6 -a 0 Current status for hub 1 [1d6b:0002 Linux 5.16.0 xhci-hcd xHCI Host Controller 0000:00:14.0, USB 2.00, 12 ports, nops] Port 6: 0103 power enable connect [045e:07b2 Microsoft Microsoft? Nano Transceiver v1.0] Sent power off request New status for hub 1 [1d6b:0002 Linux 5.16.0 xhci-hcd xHCI Host Controller 0000:00:14.0, USB 2.00, 12 ports, nops] Port 6: 0000 off $ sudo ./uhubctl -f -l 1 -p 6 -a 1 Current status for hub 1 [1d6b:0002 Linux 5.16.0 xhci-hcd xHCI Host Controller 0000:00:14.0, USB 2.00, 12 ports, nops] Port 6: 0000 off Sent power on request New status for hub 1 [1d6b:0002 Linux 5.16.0 xhci-hcd xHCI Host Controller 0000:00:14.0, USB 2.00, 12 ports, nops] Port 6: 0100 power
命令语法不太方便。下面简要解释。
uhubctl 基于 libusb,因此该工具也适用于其他操作系统。相比之下, hubpower 直接访问 /dev/bus/usb/ (或 /proc/bus/usb/)中 hub的设备文件,这仅适用于 Linux。
uhubctl 也支持 USB 3.x (参见 git commit 及其后续)。然而,似乎没有人关心当真正的 USB 设备连接到 hub时会发生什么: 我尝试重置 SuperSpeed 设备导致了奇怪的结果: 当返回 power 时,设备仍处于 Polling 状态,并且未被枚举。此外,当 power 关闭时,“lsusb -v”卡住了。所以那里发生了一些不好的事情。
这些是在 SuperSpeed 端口上关闭和重新打开电源的命令。下面是关于 SuperSpeed 的更多信息。
# ./uhubctl -f -e -l 2 -p 4 -a 0 # ./uhubctl -f -e -l 2 -p 4 -a 1
- -f 意味着与 hub 一起工作,即使 hub 报告它不支持每个单独的端口的电压控制。
- -e 的意思是“精确定位”,即将一个 SuperSpeed 端口算作两 hubs。
- -l 2 表示第二 hub (第一 hub 是我电脑上的 USB 2.0 hub )
- -p 4 表示端口数字四。
- -a 0 表示关闭电源(action #0)
- -a 1 表示打开电源(action #1)
PORT_POWER 解释
当来自主机(host)的命令到达将 PORT_POWER 更改为零时,端口无条件地进入 Powered-off 状态。即使 hub 继续使用其 5V 电源为设备供电也是如此。 PORT_POWER 变为零还有其他原因,特别是端口上的 overcurrent 条件(设备消耗过多电流)。
将 PORT_POWER 更改为 '1' 的唯一方法是通过来自主机的命令。这会将端口置于 Disconnected 状态。没有任何连接的 USB 端口通常处于这种状态。当在端口上检测到设备时,状态将在短暂延迟后更改为 Disabled 。如果设备已连接,并且 PORT_POWER 更改为 '1',则会立即发生这种情况。
从这种状态开始,激活设备的唯一途径是重置端口(对于 PORT_RESET,见下文)。只有主机可以做到这一点。因此, hub 唯一能做的就是通知主机有连接的设备需要注意。
这是端口的 status 比特之一的用武之地: PORT_CONNECTION。当端口处于 Powered-off 状态或 Disconnected 状态时,此比特(bit)必须为零。当端口从 Disconnected 状态转换为 Disabled 状态时, PORT_CONNECTION 变为 '1' 。 PORT_CONNECTION 中的更改会生成 hub event,因此会通知驱动程序(即在启用 USB_PORT_FEAT_C_CONNECTION 的情况下对 kernel的 hub.c 中的 port_event() 进行函数调用)。驱动程序通过重置端口(通过将端口的 PORT_RESET 位更改为 '1')和枚举连接的设备来响应。
这一切都与 USB 2.0有关。 USB 2.0 规范中的图 11-10 显示了 hub的端口如何更改状态。
直接控制 PORT_RESET 和 PORT_ENABLE
hubpower 还可以直接更改另外两个属性: PORT_RESET 和 PORT_ENABLE。事实上,我将此功能添加到我自己的此工具的 fork 中。但是请注意,您所能做的就是故意使 USB 设备发生故障(从而使计算机重置设备以修复该问题)。现在更详细:
主机应将PORT_RESET 设置为 '1' ,以便根据 USB 2.0 规范中的第 11.5.1.5 节启动端口复位(port reset)。 hub 在完成重置后将 PORT_RESET 更改回 '0' 。 hub 永远不会主动启动端口复位,并且不允许主机将 '0' 写入此属性(第 11.24.2.7.1.5 节)。
如果端口处于 Powered-off 或 Disconnected 状态,则此 hub 将忽略 PORT_RESET 。
关于 PORT_ENABLE: 当此属性更改为 '0'时,端口更改为 Disabled 状态。这可能是由于来自主机的请求以及 USB 设备断开连接、端口处于断电状态或复位(reset)过程中的错误所致。
PORT_ENABLE 只能根据主机的端口复位请求更改为 '1' ( USB 2.0 规范中的第 11.24.2.7.1.2 节)。
因此不允许主机将 PORT_RESET 更改为 '0' 或将 PORT_ENABLE 更改为 '1'。尝试这样做将导致来自 hub 的错误响应(相关的 ioctl() 命令将返回 error status)。
hubpower 可以通过将端口的 PORT_RESET 更改为 '1'来直接请求重置。这将重置 USB 设备,并且设备将因此忘记其总线地址。因此该设备将无法再访问。
这个指令是直接发给 hub的,所以 Linux kernel 中的 hub的驱动程序不会知道有这个发生。事实上,计算机在尝试访问 USB 设备之前不会注意到任何更改。接下来会发生什么取决于设备的驱动程序。该设备将被视为有一些硬件错误。因此,将采取某种纠错措施。这很可能包括重置。
所以直接用 hubpower 引起reset应该可以达到预期的效果,但是会有很多不必要的戏剧性。 PORT_POWER 更优雅地做到了这一点。直接 PORT_RESET 的唯一可能优势是驱动程序可能会说“嘿,这个设备确实有问题,让我们采取一些激烈的措施来修复它”。这可能会有所帮助。
至于操纵 PORT_ENABLE,同样会发生: 设备会突然消失。即使在这种情况下,计算机也不会立即知道发生了什么: 根据 USB 2.0 specification中的 11.24.2.7.2.2 节,仅当端口由于链接错误而被禁用时,才会触发 PORT_ENABLE (即 C_PORT_ENABLE)上的更改通知。该规范明确表示,此通知不是出于任何其他原因发出的。
因此,将 PORT_ENABLE 更改为零与直接更改 PORT_RESET 具有大致相同的效果。有一个缺点: 根据 USB 3.0 规范的第 10.14.2.6.1 节, PORT_ENABLE “is not supported by SuperSpeed hubs”。
SuperSpeed (USB 3.x)
首先也是最重要的: 如果您是因为 USB 3.x 设备出现问题而阅读本文,请问问自己是否真的需要 USB 3.x 提供的传输速率(data rate)。如果答案是否定的,请尝试通过不支持 USB 3.x 的 USB hub (或较短的 USB 2.0 电缆)将设备连接到计算机。仅此一项就可以解决问题。
SuperSpeed USB (与 USB 3.x含义相同)与 USB 2.0并存。每 SuperSpeed 设备实际上由两个设备组成: 一个单独的设备用于 SuperSpeed,另一个单独的设备用于 USB 2.0。这两个 USB 版本中的每一个都依赖于 USB cable的单独电线。它们在电子上和概念上都是相互独立的。
USB 规范要求每 SuperSpeed 设备都包含这两个设备,尽管实际上并不需要。换句话说,不支持 USB 2.0 的 SuperSpeed 设备在连接到 SuperSpeed 端口时可以正常工作。
当 SuperSpeed 设备连接到 SuperSpeed 端口时,首先尝试通过 SuperSpeed 接口进行连接。如果失败,将尝试通过 USB 2.0 进行连接。实际上(根据规范), USB 设备绝不会同时通过两个版本进行连接。但是,这是可能的,并且会使 USB 设备的行为就像是两个独立的设备一样。
一 SuperSpeed hub 由两个并联的 hubs 组成: 一个用于 USB 2.0 ,一个用于 SuperSpeed。当您将外部 SuperSpeed USB hub 连接到计算机时,系统会添加两 hubs 。它们显示为两个独立的设备。普通 USB 设备不允许同时使用两个版本,但 hub 必须这样做。
如果 SuperSpeed hub 连接到 USB 2.0 端口,它的行为就像一个普通的 USB 2.0 hub。
一般来说,这两 hubs 中的每一个都独立于另一个运行。每 hub 都有自己的端口,而这些端口中的每一个都独立运行。特别是,如果端口的参数在其中一个并行 hubs上发生变化,则这对另一 hub的端口没有影响。
另一个结论是,如果“lsusb -t”显示一个设备通过 SuperSpeed root hub连接到计算机,则它作为 SuperSpeed 设备运行。换句话说,它的数据速率是 5 Gbit/s 或更高。同样,如果此设备通过 USB 2.0 root hub连接,则其数据速率为 480 Mbit/s 或更低。
SuperSpeed 和 PORT_POWER
回想一下, hubpower 实际上所做的是更改端口的 PORT_POWER 属性。
但是一 SuperSpeed hub 是由两个并联的 hubs 组成的。这两 hubs 中的每一个对于每个端口都有自己独立的 PORT_POWER 属性。那么 hub 应该什么时候关闭 VBUS power呢?每个物理 power switch 都依赖于两 PORT_POWER 属性,每个并行 hubs都有一个属性。
USB 3.0 specification 的表10-2给出了 hub 是否应该开启电源的真值表。可以概括如下: 如果 hub 仅作为 USB 2.0 hub 运行(例如,连接到不支持 SuperSpeed的计算机),则它遵循 USB 2.0的 PORT_POWER。如果它作为 SuperSpeed hub 连接(或两个并联的 hubs 都连接),只有当两 PORT_POWER 都为零时, VBUS 才会关闭。
如果这听起来很复杂,那实际上是简单的部分: hub 的 SuperSpeed 部分具有不同的内部状态机(state machine)。这是意料之中的,因为 link training 的做法不同。但是这个状态机(state machine)具有三种不同的状态(而不是一种,如 USB 2.0),当 PORT_POWER 为零时,它们是符合条件的:
- DSPORT.Powered-off,这意味着端口完全处于非活动状态
- DSPORT.Powered-off-detect,表示端口尝试检测 SuperSpeed 链接伙伴
- DSPORT.Powered-off-reset,这意味着端口对 link partner 执行 warm 复位(下面更多关于 warm 复位)。
最后两个状态的目的是确保如果有自己的电源的 SuperSpeed 设备连接到端口,则连接不会回退到 USB 2.0。发生这种情况是因为设备无法识别它已连接到 SuperSpeed 端口。
那么我们从中学到了什么?主要是通过在 SuperSpeed hub 上更改 PORT_POWER 来重置设备并不像在 USB 2.0 hub上那么简单。
SuperSpeed: warm 复位和 hot 复位
USB 2.0 有一种重置设备的简单方法: 两条线都通过 hub 连接到接地(SE0) 10 毫秒。另一方面, SuperSpeed 设备有两种复位设备的方法: warm 复位和 hot 复位。这些不应与 PowerOn 复位和 Inband 复位混淆,它们是用于定义重置原因的术语。 Inband 复位表示重置是由于主机的请求而发生的。这可能导致 warm 复位或 hot 复位,具体取决于请求的类型(更多内容见下文)。
区分 warm 复位和 hot 复位很重要: 特别是, warm 复位涉及停止数据数据流(data stream)并从头开始启动它。 hot 复位在数据数据流本身上发送,并保持此数据数据流运行。因此 hot 复位的速度要快得多,但如果数据数据流出现问题可以通过重启来解决,则需要 warm 复位。这种问题的一个例子是 physical layer上是否有比特 errors (bit errors)。取下比特流(bitstream)并重新开始可能会解决此问题,可能是因为它可以纠正均衡器(equalizer)的次优调整。
回想一下, usbreset 执行 ioctl() 和 USBDEVFS_RESET。在发生的所有其他事情中,这会将 PORT_RESET 更改为零,而不管设备的 USB 版本(从 Linux kernel v5.16开始)。
根据 USB 3.0 规范第 7.4.2 节, PORT_RESET 请求会产生 Hot 复位。这意味着如果数据数据流处于活动状态,则不会被拆除,而是在这个数据数据流(data stream)上发送复位命令。该规范还定义了 BH_PORT_RESET (feature 编号 28),它强制使用 warm 复位(除非禁用端口)。此重置更为基本: 它关闭数据数据流,并重新启动程序以再次启动它(通过 LFPS signaling)。重要的区别在于,如果数据数据流需要重启, BH_PORT_RESET 会执行,但 PORT_RESET 不会。
新 SuperSpeed 设备的连接涉及 Warm 复位。所以很遗憾,显然没有工具可以在 SuperSpeed 端口上使用 PORT_POWER 来解决问题。上文提到, uhubctl 在技术上可以做到这一点,但设备最终陷入了混乱状态。
剩余的笔迹
这些是可能有用的随机信息,但没有适当的上下文。
- PORT_POWER 等命令代码在 USB 2.0 规范的表11-17和 USB 3.0 规范的表10-8中列为 Hub class feature selectors 。
- kernel 中启用 PORT_RESET 的函数是 hub_port_reset()。但要真正重新初始化端口,需要 usb_reset_device(),它还为拥有的驱动程序准备重置,然后调用 usb_reset_and_verify_device()的函数。所有这些功能都在 drivers/usb/core/hub.c中定义,只有 usb_reset_device() 被导出。
- 在 drivers/usb/core/devio.c中, usb_reset_device() 被 proc_resetdevice()调用,可以由相关设备文件上的一 USBDEVFS_RESET ioctl() 触发。这就是 usbreset 所做的(见上文)。
- 这相当于 linux_usbfs.c 中 libusb的 op_reset_device() (用 IOCTL_USBFS_RESET 代替,但等于 USBDEVFS_RESET ,都是20,对比 kernel的 include/uapi/linux/usbdevice_fs.h)。 libusb的函数也取消声明接口。如果 device的任何接口被占用,proc_resetdevice() 将礼貌地拒绝。
- 在 hub.c中, port_event() 被 hub_event()调用。前者是检测端口位变化的那个。 hub_event() 是 work item,由 kick_hub_wq() 启动(特别是 hub_irq(),其中“fires on port status changes and various faults”响应到达 hub的 IN endpoint上的状态更改通知,这是指定用于此目的)。