01signal.com

在 Linux 上复位 USB 设备(并可能控制其电源)

引言

在理想世界里,USB 设备与 USB 集线器(hub)都会规规矩矩地工作,并能自行解决遇到的各种问题。但现实中,各类诡异的事情都可能发生,因此有必要做点什么来应对。通常的做法是把 USB 插头拔下来再重新插上。但如果这个过程需要自动化呢?

更极端的解决方案见另一篇网页:它会关闭并重启 USB 集线器的驱动,效果几乎等同于把电脑上所有 USB 设备拔掉再重新连接。这是最重的一把锤子,但有时确实需要用到。

本页介绍两种复位(reset)指定 USB 设备的方法。我先演示每种方法的操作步骤,并附少量必要的说明;之后会深入讲解大量技术细节。

这里的讨论仅限于 Linux,尽管第二种方法在其他平台上可能同样可行。

这里的很多资料取自 USB 2.0 规范(Universal Serial Bus Specification Revision 2.0)。这份文档的文件名通常是 usb_20.pdf。

方法一:让 Linux 内核复位该设备

有好几个工具可以做这件事,其中功能最全面的工具是 usbutils 的一部分。下载 usbreset.c,然后用 gcc 编译:

$ 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

复位之后,内核日志中会出现下面一行:

usb 1-6: reset full-speed USB device number 4 using xhci_hcd

这段操作说明了几件事:

那么 usbreset 究竟做了什么呢?归根结底,就是对 USB 设备的设备文件执行下面这个命令:

ioctl(fd, USBDEVFS_RESET, 0)

这会最终调用内核中的 usb_reset_device() 函数,而该函数做的事情远不止是复位设备。其设计目的是让整个过程平稳进行:如果设备驱动要求收到通知,它会在复位前后分别通知驱动;复位前会解绑(unbind)驱动,复位后再重新绑定(bind)回去。复位后还会重新加载设备的配置。否则,设备将不知道自己的总线地址,也无法为任何数据交换做好准备。

所以这几乎等同于重新连接设备,只是不再要求设备自报身份(因为这些信息已经知道了),也不会给它分配新的地址。

就 USB 3.x 而言,这是一种热复位(hot reset),属于效果较弱的那种复位方式。下文还会详细介绍热复位。

方法二:切换 USB 端口的电源状态

用于这个目的的工具也有好几个。下面用 hubpower 来演示。下载 hubpower.c,然后编译:

$ gcc -O3 -Wall -g hubpower.c -o hubpower

使用这个工具会稍微麻烦一些,因为命令并不是发送给 USB 设备本身,而是发送给该 USB 设备所连接的 USB 集线器。

因此第一步要弄清楚目标集线器是哪一个。先找到 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

可以看到,设备位于总线编号 1、设备编号 4。注意,每次 USB 设备被枚举时,设备编号都会变化。

那么,这个 USB 设备到底连在哪个集线器上?又连在哪个端口上?

$ 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 号端口上。该集线器的总线编号是 1,设备编号也是 1(它是主板上的 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”这部分是集线器的总线地址。如果这是外接集线器,那么每次集线器连接到电脑时,这个地址都会变化。

但端口编号永远不会变(只要设备始终连接在同一个物理端口上)。

既然知道了设备所在的端口号,下面就来复位它:

$ 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

执行第一条命令后,内核日志会报告设备已断开连接:

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
[ ... ]

由于发生了重新枚举,设备被分配了一个新的总线地址:

$ 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 集线器很少。下文会详细说明。

对绝大多数集线器来说,第一条命令(“power off”)只是让端口进入一种会忽略设备的状态;第二条命令(“power on”)则让端口回到正常状态。结果是设备被检测到,并开始执行初始化流程。具体来说,设备会被复位、枚举,然后由其驱动程序完成初始化。

因此,物理断开通常比这些命令更有效,尤其是当设备依靠 USB 插头供电时。但如果集线器真的能切断 VBUS,hubpower 的效果与之相同。

注意,在这个例子中,命令是发送给主板上的 USB 控制器(即根集线器)的,因为该 USB 设备直接连接在电脑上。如果设备通过外接集线器连接电脑,则应该把命令发送给那个外接集线器。两种情况下 hubpower 的使用方法都一样。

遗憾的是,hubpower 不支持 USB 3.0(SuperSpeed)。下文还会继续讨论。

两种方法的区别

那么,第二种方法(hubpower)和第一种方法(usbreset)有什么不同呢?就解决设备问题而言,两种方法的本质是一样的:都发送一个复位命令。两者发送命令的方式差别巨大,但如果问题真能被解决,最终起作用的仍然都是这个复位命令。

但两者之间有几个重要差异:

控制电气设备(?)

虽然本文的主要话题是如何解决 USB 设备的问题,但这还有一个有趣的副作用:有时可以控制 USB 端口的 5V 电源。换句话说,一个简单便宜的 USB 集线器,可以用来打开或关闭一个能承受 2.5 瓦功率的电源。

这用来控制一个机电式继电器(electromechanical relay)绰绰有余。因此,若要控制 110V / 220V 供电的电器,只需要再增加一个元件。当然,最好也加一个简单的二极管,以保护 USB 集线器不被损坏。除此之外,就不需要别的了。

遗憾的是,这种电源控制能力并不是必备的:根据 USB 2.0 规范第 11.11 节,集线器可以配备电源开关,在端口处于 Powered-Off(已断电)状态时切断该端口的 5V 电源;也可以用一个电源开关同时控制多个端口的电源,也就是“成组电源切换”(ganged power switching)。控制电源的主要目的是关闭电流过大的 USB 设备,使其余端口能继续正常工作。

但如前所述,物理上能否控制电源是可选的。hubpower 真正做的是改变端口的 PORT_POWER 属性的值(在 USB 规范中,这个属性称为一个 “feature”,可译作“特性”)。这一改变涉及集线器端口的两个不同方面:

下文会对 PORT_POWER 作更详细的说明。

我的集线器能控制电压吗?

你怎么知道自己的集线器是否真的会切断电压?唯一可靠的方法就是实测。接一个不是 USB 设备、但会从 USB 端口取电的东西来测试。真正的 USB 设备反而可能造成混淆。例如,光电鼠标在收到断电命令后通常会关闭 LED,即使 5V 电压仍然存在。

每个集线器都会在“lsusb -v”可见的信息中声明自己是否控制电压,以及控制到什么粒度。这段信息位于 Hub Descriptor(集线器描述符)中;该描述符在规范的 11.23.2.1 节中有定义。集线器可以声明自己完全不控制电压,也可以声明按端口组(“gang”)控制电压,还可以声明单独控制每个端口的电压。根据 USB 2.0 规范第 11.11 节:“带电源开关的集线器可以对所有端口成组(gang)切换电源,也可以单独切换每个端口的电源,还可以有任意数量的组,每组包含一个或多个端口。”

不过,这条信息并不可靠。我遇见过好几个宣称自己能够控制电压的集线器,结果没有一个真的能做到。

例如,下面是“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

看起来挺乐观,对吧?实际上,这个集线器根本不能控制电压。

相比之下,下面是一个普通主板集线器的输出:

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 Port Status”(集线器端口状态)。这些状态位定义在 USB 2.0 规范的表 11-21(第 11.24.2.7.1 节)中。hubpower 获取的正是这些信息。

uhubctl 工具

用 USB 集线器控制继电器的想法,促成了很多项目。值得一看的是 uhubctl,主要是因为该项目维护了一个能够控制电压的 USB 集线器列表。

这个工具显然只打算用于电压控制,而不是用来解决 USB 设备的问题。例如,uhubctl 默认会忽略那些没有声明支持逐端口独立控制电压的集线器。

下面的命令与前面 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 的命令语法要麻烦得多。下面简要解释一下。

uhubctl 基于 libusb,因此也能在其他操作系统上运行。相比之下,hubpower 直接访问位于 /dev/bus/usb/(或 /proc/bus/usb/)下的集线器设备文件,因此只能在 Linux 上运行。

uhubctl 也支持 USB 3.x(参见这个 git 提交及其后续提交)。不过,似乎没有人关心当真有一个真实 USB 设备连接在集线器上时会发生什么:我尝试复位 SuperSpeed 设备时得到了一些奇怪的结果。电源恢复后,设备停在 Polling(轮询)状态,没有被枚举;而且在断电期间,“lsusb -v”还会卡住。可见那里确实出了些问题。

以下命令用于在 SuperSpeed 端口上关闭和重新打开电源。关于 SuperSpeed 的更多内容见下文。

# ./uhubctl -f -e -l 2 -p 4 -a 0
# ./uhubctl -f -e -l 2 -p 4 -a 1

详解 PORT_POWER

当主机发出命令要把 PORT_POWER 改为零时,端口会无条件进入 Powered-off(已断电)状态。即使集线器仍在对设备提供 5V 电源,情况也是如此。PORT_POWER 变为零还有其他原因,尤其是端口出现过流(overcurrent)时,也就是设备抽取的电流过大。

要把 PORT_POWER 改为 '1',唯一的途径是收到来自主机的命令。这会使端口进入 Disconnected(未连接)状态。没有连接任何设备的 USB 端口通常就处在这个状态。当端口上检测到设备时,经过短暂延迟后会进入 Disabled(已禁用)状态。如果设备已经连接着,而 PORT_POWER 变为 '1',则会立即进入 Disabled 状态。

要从这个状态过渡到设备可用状态,唯一途径是复位端口(使用 PORT_RESET,见下文)。只有主机才能执行复位。因此,集线器唯一能做的,就是通知主机有设备连接着、需要处理。

这里就涉及端口的状态位之一:PORT_CONNECTION。当端口处于 Powered-off 或 Disconnected 状态时,该位必须为零。当端口从 Disconnected 状态转换到 Disabled 状态时,PORT_CONNECTION 会变为 '1'。PORT_CONNECTION 的变化会产生一个集线器事件(hub event),从而通知驱动程序:也就是说,内核 hub.c 中的 port_event() 函数会以启用 USB_PORT_FEAT_C_CONNECTION 的方式被调用。驱动程序的响应是复位端口(把端口的 PORT_RESET 位设为 '1'),并对所连接的设备进行枚举。

以上这些都和 USB 2.0 有关。USB 2.0 规范中的图 11-10 展示了集线器端口的状态转换过程。

直接控制 PORT_RESET 与 PORT_ENABLE

hubpower 还可以直接改变另外两个属性:PORT_RESET 和 PORT_ENABLE。实际上,这个能力是我加到我自己的这个工具 fork(分支)版本中的。但请注意,你用它唯一能做的,就是故意让 USB 设备出错(然后让电脑复位设备来修复这个问题)。下面更详细地说明:

根据 USB 2.0 规范第 11.5.1.5 节,主机应将 PORT_RESET 设为 '1',以发起端口复位。集线器在完成复位后会把 PORT_RESET 重新置回 '0'。集线器绝不会自行发起端口复位;同时,主机也不允许向该属性写入 '0'(第 11.24.2.7.1.5 节)。

如果端口处于 Powered-off(已断电)或 Disconnected(未连接)状态,集线器会忽略 PORT_RESET。

关于 PORT_ENABLE:当该属性变为 '0' 时,端口会进入 Disabled(已禁用)状态。这可能是主机请求的结果,也可能是 USB 设备断开、端口处于断电状态或复位过程中出现错误所致。

PORT_ENABLE 只有在主机发出端口复位请求后,才能变为 '1'(USB 2.0 规范第 11.24.2.7.1.2 节)。

因此,主机不允许把 PORT_RESET 改为 '0',也不允许把 PORT_ENABLE 改为 '1'。如果尝试这样做,集线器会返回错误响应(相关的 ioctl() 命令会返回错误状态)。

hubpower 可以把端口的 PORT_RESET 改为 '1',直接请求一次复位。这会复位 USB 设备,设备也会因此忘记自己的总线地址,从而不再可访问。

这条命令是直接发送给集线器的,因此 Linux 内核中的集线器驱动并不会知道发生了这件事。实际上,电脑在尝试访问该 USB 设备之前,不会察觉任何变化。接下来会发生什么,取决于设备驱动。设备会像发生了硬件错误一样被对待,于是会采取某种纠错措施。最可能的情况就是执行一次复位。

所以,用 hubpower 直接触发复位虽然很可能达到预期效果,但会带来很多不必要的麻烦。用 PORT_POWER 则优雅得多。直接使用 PORT_RESET 唯一可能的好处是,驱动可能会认为:“嘿,这个设备真的出问题了,我们得采取更彻底的措施来修复它。”而这也许确实能解决问题。

至于操作 PORT_ENABLE,也会发生类似的情况:设备会突然消失。即便如此,电脑也不会立刻知道发生了什么。根据 USB 2.0 规范第 11.24.2.7.2.2 节,只有在链路上发生错误导致端口被禁用时,才会触发 PORT_ENABLE 的变化通知(即 C_PORT_ENABLE)。规范明确指出,其他任何原因都不会触发此通知。

因此,把 PORT_ENABLE 改为零的效果与直接改变 PORT_RESET 大致相同,但有一个缺点:根据 USB 3.0 规范第 10.14.2.6.1 节,SuperSpeed 集线器“不支持 PORT_ENABLE”。

SuperSpeed(USB 3.x)

首先是最重要的一点:如果你是因为 USB 3.x 设备出了问题才读到这篇文章,请先问问自己,是否真的需要 USB 3.x 提供的数据速率。如果答案是否定的,试着把设备通过一个不支持 USB 3.x 的 USB 集线器(或一根较短的 USB 2.0 线缆)连接到电脑。仅此一项也许就能解决问题。

SuperSpeed USB(也就是 USB 3.x)与 USB 2.0 以并行方式共存。每个 SuperSpeed 设备实际上由两个设备组成:一个用于 SuperSpeed 的独立设备,以及另一个用于 USB 2.0 的独立设备。这两个 USB 版本分别使用 USB 线缆中不同的导线,在电气上和概念上彼此独立。

USB 规范要求每个 SuperSpeed 设备都由这两个设备组成,尽管在实际使用中并非必须如此。换句话说,一个不支持 USB 2.0 的 SuperSpeed 设备,连接到 SuperSpeed 端口时也能正常工作。

当一个 SuperSpeed 设备连接到 SuperSpeed 端口时,系统会首先尝试通过 SuperSpeed 接口进行连接。如果失败,则会尝试通过 USB 2.0 连接。在实际中(规范也是这样说的),USB 设备绝不会同时通过两个版本进行连接。不过,这种情况是可能的;如果真的发生,USB 设备会表现得像两个独立的设备一样。

SuperSpeed 集线器由两个并行的集线器组成:一个用于 USB 2.0,一个用于 SuperSpeed。当你把外接的 SuperSpeed USB 集线器连接到电脑时,系统中会增加两个集线器,它们表现为两个独立的设备。普通 USB 设备不允许同时使用两个版本,但集线器必须如此。

如果 SuperSpeed 集线器连接到 USB 2.0 端口,它就会像一个普通 USB 2.0 集线器那样工作。

一般来说,这两个集线器彼此独立工作。每个集线器都有自己的端口,而且每个端口也独立工作。尤其要注意,如果改变了其中一个并行集线器上某个端口的参数,不会影响另一个集线器的端口。

由此还可得出一个结论:如果“lsusb -t”显示某个设备通过 SuperSpeed 根集线器连接到电脑,那么它就是在以 SuperSpeed 设备的方式工作,也就是说它的数据速率是 5 Gbit/s 或更高。同样,如果设备是通过 USB 2.0 根集线器连接的,那么它的数据速率是 480 Mbit/s 或更低。

SuperSpeed 与 PORT_POWER

回顾一下:hubpower 实际所做的,就是改变端口的 PORT_POWER 属性。

但 SuperSpeed 集线器由两个并行的集线器组成。这两个集线器各自拥有相互独立的每端口 PORT_POWER 属性。那么,集线器应该在什么时候切断 VBUS 电源呢?每个物理电源开关都取决于两个 PORT_POWER 属性,分别来自两个并行集线器中的每一个。

USB 3.0 规范中的表 10-2 给出了集线器是否应启用电源的真值表(truth table)。可以概括如下:如果集线器仅作为 USB 2.0 集线器运行(例如连接到一个不支持 SuperSpeed 的电脑),则遵循 USB 2.0 的 PORT_POWER。如果作为 SuperSpeed 集线器连接(或者说两个并行集线器都已连接),那么只有当两个 PORT_POWER 都为零时,VBUS 才会被切断。

如果上面听起来已经很复杂了,其实那还算是容易的部分:集线器的 SuperSpeed 部分有着不同的内部状态机(state machine)。这并不意外,因为链路训练(link training)的方式不同。而且,当 PORT_POWER 为零时,这个状态机有三个可能的状态(而 USB 2.0 只有一个):

后两个状态的目的是:如果一个自带电源的 SuperSpeed 设备连接到了这个端口,要确保连接不会回退到 USB 2.0。如果没有这两个状态,就会发生回退,因为该设备没有任何办法知道自己连接的是一个 SuperSpeed 端口。

那么我们从这些内容里学到了什么?主要是:在 SuperSpeed 集线器上通过改变 PORT_POWER 来复位设备,并不像在 USB 2.0 集线器上那样简单。

SuperSpeed:温复位与热复位

USB 2.0 只有一种简单的复位设备方式:集线器把两条信号线都接地(SE0)并保持 10 ms。相比之下,SuperSpeed 设备有两种复位方式:温复位(warm reset)和热复位(hot reset)。不要把它们与 PowerOn Reset(上电复位)和 Inband Reset(带内复位)相混淆——这两个术语用于定义复位发生的原因。Inband Reset 表示复位是因为主机请求而发生的;根据请求类型的不同,它可能导致温复位或热复位(下面马上会细说)。

区分温复位和热复位十分重要:温复位需要停止数据流(data stream),并从零开始重新建立;热复位则是直接在数据流上发送的,并让数据流保持运行。因此热复位要快得多,但如果数据流的问题可以通过重启数据流来解决,那就需要温复位。这类问题的一个例子是物理层出现位错误(bit errors)。把比特流(bitstream)关闭再从头开始,或许能解决问题,原因可能是这样做能纠正均衡器(equalizer)不理想的调谐。

回顾一下,usbreset 是以 USBDEVFS_RESET 参数执行 ioctl()。在由此引发的一系列操作中,有一件是把 PORT_RESET 改为 0,无论设备属于哪个 USB 版本(就 Linux 内核 v5.16 而言)。

根据 USB 3.0 规范第 7.4.2 节,PORT_RESET 请求会导致热复位(Hot Reset)。这意味着,如果数据流处于活动状态,它不会被拆除,而是在这条数据流上发送复位命令。该规范还定义了 BH_PORT_RESET(特性编号 28),它会强制进行一次温复位(除非端口处于禁用状态)。这种复位更彻底:它会关闭数据流,并重新启动数据流的建立过程(借助 LFPS 信令)。重要的区别在于:如果数据流需要重启,BH_PORT_RESET 能做到,而 PORT_RESET 做不到。

连接一个新的 SuperSpeed 设备时会涉及一次温复位(Warm Reset)。所以,显然还没有什么工具能在 SuperSpeed 端口上利用 PORT_POWER 达到同样的效果,这挺遗憾的。如前所述,uhubctl 在技术上可以做到,但最终设备会处于一种乱七八糟的状态。


零散补充

下面这些零散的信息也许有用,但放在前面的任何地方都不太合适。

本页由机器从英文翻译而来。如有疑问,请参阅原文。
Copyright © 2021-2026. All rights reserved. (dcc38493)