Introduction
In a perfect world, USB devices and hubs behave nicely, and manage to fix whatever problem they encounter. In reality, all kinds of weird things happen, and there's a need to do something about it. The common solution is to pull out the USB plug and connect the device again. But what if this needs to happen automatically?
The most dramatic solution is presented on a different web page: It causes a shutdown and restart of the USB hub's drivers. The effect is nearly equivalent to disconnecting and connecting back all USB devices on the computer. That's the heaviest hammer, and it's necessary sometimes.
This page presents two methods to reset one specific USB device. I'll start with presenting how to carry out each method, with a few minimal explanations. After this, I'll get into a lot of technical details.
The entire discussion is limited to Linux here, even though the second method that is presented here is probably possible on other platforms as well.
A lot of information here is taken from the Universal Serial Bus Specification Revision 2.0. The file name of this document is typically usb_20.pdf.
Method #1: Ask the Linux kernel to reset the device
There are several tools available for doing this. The most advanced tool is part of usbutils. Download usbreset.c and use gcc for its compilation:
$ gcc -O3 -Wall -g usbreset.c -o usbreset
And then:
$ ./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
In response to the resets, the following appears in the kernel log:
usb 1-6: reset full-speed USB device number 4 using xhci_hcd
What this session demonstrates:
- Without any arguments, usbreset responds with a list of USB devices. The same information is obtained with lsusb.
- You must be root to actually reset a device (hence sudo).
- The device can be chosen by its bus address (bus number and device number). Note that the bus address changes every time the device is connected.
- The device can also be chosen by its Vendor / Product IDs. This is the preferred method when there is only one USB device of its sort.
- The device did not change its address on the USB bus (it wasn't re-enumerated as a result of the reset).
So what does usbreset do? It boils down to this command on the USB device's device file:
ioctl(fd, USBDEVFS_RESET, 0)
This ends up with a function call to the kernel's usb_reset_device(), which does much more than just to reset the device. The idea is to make the whole process smooth: This function notifies the device's driver about the reset before and after, if so requested. It unbinds the driver before the reset, and binds it back afterwards. The device's configuration is also loaded after the reset. Without this, the device wouldn't know its bus address and wouldn't be ready for any data exchange.
So it's almost like reconnecting the device, but without asking it to identify itself (because the information is already known) and without assigning it with a new address.
For USB 3.x, this is a hot reset, which is the less efficient type. More on hot reset below.
Method #2: Toggle the power state of the USB port
There are several tools for this purpose as well. I shall demonstrate this method with hubpower. Download hubpower.c and perform a compilation:
$ gcc -O3 -Wall -g hubpower.c -o hubpower
Using this tool is a somewhat trickier, because the commands are not sent to the USB device itself. Instead, the requests are sent to the USB hub that the USB device is connected to.
So the first step is to figure out which hub this is. First, let's find the address of the USB device:
$ 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
So the device is on bus number 1 and device number 4. Note that the device number changes every time the USB device is enumerated.
Which hub is the USB device connected to, then? And to which port?
$ 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
So device number 4 is connected to port #6. The hub's bus number is 1 and its device number is 1 (it's the motherboard's USB controller, which functions as a root hub).
Let's see what hubpower has to say about this:
$ 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
Note that the "1:1" part is the hub's bus address. If this is an external hub, this address will change every time the hub is connected to the computer.
But the numbers of the ports never change (as long as the device is connected to the same physical port).
Now when we know the device's port number, let's reset it:
$ 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
After the first command, the device will be reported as disconnected in the kernel log:
usb 1-6: USB disconnect, device number 4
After the second command, the computer behaves as if the device was connected physically:
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
[ ... ]
Because of this re-enumeration, a new bus address is assigned to the device:
$ 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
So is this like physically disconnecting the USB device and connect it back? The answer is maybe: In most cases, the hubpower commands don't really turn off the USB device's power supply. The device will therefore continue to receive its VBUS voltage (5V). Very few USB hubs actually turn off the power. More about this below.
With the vast majority of hubs, the first command ("power off') only puts the port in a state that causes it to ignore the device. The second command ("power on") returns the port to its natural state. The result is that the device is detected, and its initialization procedure begins. Among others, the device is reset and enumerated, and its driver initializes it.
So a physical disconnection is usually more effective than these commands, in particular if the device receives its power supply from the USB plug. But if the hub really shuts down VBUS, hubpower is as effective.
Note that in this example, the commands were sent to the motherboard's USB controller (the root hub). The reason is that the USB device was connected directly to the computer. If this device is connected to the computer through an external hub, the commands should be sent to this external hub. In both cases, hubpower is used in the same way.
hubpower doesn't support USB 3.0 (SuperSpeed), unfortunately. More on that below.
Differences between the two methods
So how is the second method (with hubpower) different from the first method (with usbreset)? For the purpose of solving a problem with a device, both methods do essentially the same: They send a reset command. The way this happens is dramatically different, but it's still this reset command that will probably solve the problem, if at all.
But there are a few important differences:
- hubpower works even if the device is not recognized on the bus. For example, if the computer tried to enumerate the device several times and then gave up. In this case, the device can be physically connected to the computer, but usbreset is useless because the device has is no bus address.
- Only hubpower can help when the USB port will be disabled: Sometimes the computer ignores a USB port completely after several errors with this port. hubpower should solve this.
- hubpower doesn't work with SuperSpeed (USB 3.x). It's probably a matter of adding support. See more about SuperSpeed below.
- hubpower may also control the USB device's power supply (but usually this doesn't happen). This is an advantage if the device needs a power recycle to solve the problem.
- hubpower is more difficult to work with: It is required to find which of the hub's ports is connected to the USB device. And also the hub's bus address.
Controlling electrical equipment (?)
Even though the main topic of this page is how to fix a problem with a USB device, there's an interesting side effect too: It's sometimes possible to control the USB port's 5V power supply. In other words, a simple and cheap USB hub can be used to turn on and off a power supply that is capable to handle 2.5 Watts.
This is more than enough to control an electromechanical relay. So only one component needs to be added in order to control an electrical appliance that runs on 110V / 220V. Well, it's a good idea to add a simple diode too, in order to protect the USB hub from getting damaged. But that's it.
Unfortunately, the ability to control the power supply is optional: According to section 11.11 in the USB 2.0 specification, a hub may have power switches that turn off the 5V power supply to a port when the port is in the Powered-Off state. Alternatively, a power switch may be used to control the power supply of several ports ("ganged power switching"). The purpose of controlling the power supplies is primarily to shut off USB devices that draw too much current, so that the remaining ports can continue to work normally.
But as already mentioned, the physical power control is optional. What hubpower actually does is to change the value of the port's PORT_POWER attribute (it's called a "feature" in the USB specifications). This change relates to two different aspects of the hub's port:
- Mandatory: The logical state of the port. When PORT_POWER is zero, the port can only be in the Powered-Off state (or Not Configured). In other words, the hub must behave as if there is no voltage on the port, and hence ignore any connected device, if such is present.
- Optional: The existence of VBUS (the 5V voltage) on the port's power supply wires. If the hub doesn't support this feature, the voltage may be present even when PORT_POWER is zero.
PORT_POWER is explained in more detail below.
Does my hub control the voltage?
How do you know if your hub actually turns off the voltage? The only way to know for sure is to test it. Connect anything that isn't a USB device, but consumes power from the USB port. Real USB devices may add confusion. For example, an optical mouse will usually turn off its LED in response to the power-off command, even if the 5V voltage remains.
Each hub declares whether it controls the voltage, and to what granularity, in the information that is visible with "lsusb -v" (in the Hub Descriptor, which is defined in section 11.23.2.1 of the specification). The hub may declare that it doesn't control the voltage, or that it controls the voltage in groups of ports ("gangs"), or that it controls the voltage of each port individually: According to section 11.11 in the USB 2.0 specification, "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 ports".
However, this information is not reliable. I've encountered several hubs that declare that they control the voltage, but none of them did.
For example, this is an of the output of "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
Looks optimistic, doesn't it? In reality, this hub doesn't control the voltages at all.
By contrast, this is a plain motherboard's 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
So this hub admits that it doesn't support any voltage control.
Note that besides the information about the power switching, there's also status info about each port as "Hub Port Status". These status bits are defined in table 11-21 of the USB 2.0 specification (section 11.24.2.7.1). This is the same information that is fetched by hubpower.
The uhubctl tool
The idea to control a relay with a USB hub is the motivation for a lot of initiatives. It's worth to look at uhubctl, mainly because this project maintains a list of USB hubs that control the voltage.
This tool is clearly intended only for voltage control, and not for solving problems with a USB device. For example, by default uhubctl ignores hubs that don't declare the ability to control the voltages individually for each port.
These commands are equivalent to the hubpower commands above:
$ 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
The command syntax is much less convenient. It's explained briefly below.
uhubctl is based upon libusb, so this tool works with other operating systems as well. By contrast, hubpower accesses the hub's device file in /dev/bus/usb/ (or /proc/bus/usb/) directly, which works only on Linux.
uhubctl also supports USB 3.x (see the git commit and its follow-up). However, it seems like nobody cared what happens when a real a USB device is connected to the hub: My attempts to reset a SuperSpeed device resulted in weird results: When the power was returned, the device was left in the Polling state, and it wasn't enumerated. Also, while the power was off, "lsusb -v" was stuck. So something bad happened there.
These are the commands to turn the power off and on again on a SuperSpeed port. More about SuperSpeed below.
# ./uhubctl -f -e -l 2 -p 4 -a 0 # ./uhubctl -f -e -l 2 -p 4 -a 1
- -f means to work with a hub even if the hub reports that it doesn't support voltage control for each individual port.
- -e means "exact position", i.e. count a SuperSpeed port as two hubs.
- -l 2 means the second hub (the first hub was the USB 2.0 hub on my computer)
- -p 4 means the port number four.
- -a 0 means turn off power (action #0)
- -a 1 means turn on power (action #1)
PORT_POWER explained
When a command arrives from the host to change PORT_POWER to zero, the port unconditionally enters the Powered-off state. This is true even if the hub continues to feed the device with its 5V power supply. There are other reasons for PORT_POWER to become zero, in particular an overcurrent condition on the port (the device draws too much current).
The only way to change PORT_POWER to '1' is by virtue of a command from the host. This puts the port in the Disconnected state. USB ports that have nothing connected to them are normally in this state. When a device is detected on the port, the state will change to Disabled after a short delay. If the device is already connected, and PORT_POWER changes to '1', this happens immediately.
From this state, the only path towards activating the device is by resetting the port (with PORT_RESET, see below). Only the host can do that. Hence the only thing that the hub can do is to notify the host that there is connected device that needs attention.
This is where one of the port's status bits comes in: PORT_CONNECTION. This bit must be zero when the port is in the Powered-off state or the Disconnected state. PORT_CONNECTION changes to '1' when the port transitions from the Disconnected state to the Disabled state. A change in PORT_CONNECTION generates a hub event, so the driver is notified (i.e. a function call to port_event() in the kernel's hub.c is made with USB_PORT_FEAT_C_CONNECTION enabled). The driver responds with resetting the port (by changing the port's PORT_RESET bit to '1') and enumerating the connected device.
All this relates to USB 2.0. Figure 11-10 in the USB 2.0 specification shows how a hub's port changes states.
Controlling PORT_RESET and PORT_ENABLE directly
hubpower also has the capability to change two other attributes directly: PORT_RESET and PORT_ENABLE. In fact, I added this capability to my own fork of this tool. Note, however, that all you can do with this is to deliberately make the USB device fail (and hence make the computer reset the device to fix that). Now in more detail:
PORT_RESET should be set to '1' by the host in order to initiate a port reset according to section 11.5.1.5 in the USB 2.0 specification. The hub changes PORT_RESET back to '0' after completing the reset. The hub never starts a port reset on its own initiative, and the host is not allowed to write '0' to this attribute (section 11.24.2.7.1.5).
This hub will ignore PORT_RESET if the port is in the Powered-off or Disconnected states.
Regarding PORT_ENABLE: When this attribute changes to '0', the port changes to the Disabled state. This can occur due to a request from the host, as well as disconnection of the USB device, the port being in a powered-off state, or an error during the reset process.
PORT_ENABLE can change to '1' only as a result of a port reset request from the host (section 11.24.2.7.1.2 in the USB 2.0 specification).
The host is hence not allowed to change PORT_RESET to '0' or to change PORT_ENABLE to '1'. Trying to do this will result in an error response from the hub (the relevant ioctl() command will return an error status).
hubpower can request a reset directly, by changing the port's PORT_RESET to '1'. This will reset the USB device, and the device will forget its bus address as a result. So the device will not be accessible anymore.
This command is sent directly to the hub, so the hub's driver in the Linux kernel will not know that this has happened. In fact, the computer will not notice that anything has changed until it attempts to access the USB device. What will happen next depends on the device's driver. The device will be treated as if it has some hardware error. Accordingly, some kind of error-correcting measure will be taken. Most likely, this will include a reset.
So using hubpower to cause a reset directly will probably achieve the desired result, but with a lot of unnecessary drama. PORT_POWER does this more elegantly. The only possible advantage of a direct PORT_RESET is that the driver might say "hey, something is really wrong with this device, let's do something drastic to fix it". And that might help.
As for manipulating PORT_ENABLE, the same will happen: The device will suddenly disappear. Even in this case, the computer will not know immediately that anything has happened: According to section 11.24.2.7.2.2 in the USB 2.0 specification, a change notification on PORT_ENABLE (i.e. C_PORT_ENABLE) is triggered only if the port becomes disabled because of an error on the link. The spec explicitly says that this notification is not made for any other reason.
So changing PORT_ENABLE to zero will have roughly the same effect as changing PORT_RESET directly. With one disadvantage: According to section 10.14.2.6.1 of the USB 3.0 specification, PORT_ENABLE "is not supported by SuperSpeed hubs".
SuperSpeed (USB 3.x)
First and foremost: If you're reading this because you have a problem with a USB 3.x device, ask yourself if you really need the data rate that USB 3.x offers. If the answer is negative, try connecting the device to the computer through a USB hub that doesn't support USB 3.x (or a short USB 2.0 cable). This alone might solve the problem.
SuperSpeed USB (which means the same as USB 3.x) coexists in parallel with USB 2.0. Every SuperSpeed device effectively consists of two devices: One separate device for SuperSpeed, and another separate device for USB 2.0. Each of these two USB versions rely on separate wires of the USB cable. They are mutually independent, electronically and conceptually.
The USB specifications requires that every SuperSpeed device consists of these two devices, even though this is practically not required. In other words, a SuperSpeed device that doesn't support USB 2.0 works properly when it's connected to a SuperSpeed port.
When a SuperSpeed device is connected to a SuperSpeed port, the first attempt is to connect through the SuperSpeed interface. If that fails, an attempt to connect through the USB 2.0 is made. In practice (and according to the specification), a USB device never connects through both versions at the same time. However, this is possible, and will make the USB device behave as if it was two separate devices.
A SuperSpeed hub consists of two hubs in parallel: One for USB 2.0 and one for SuperSpeed. When you connect an external SuperSpeed USB hub to a computer, two hubs are added to the system. They appear as two separate devices. A plain USB device is not allowed to use both versions in parallel, but a hub must do this.
If a SuperSpeed hub is connected to a USB 2.0 port, it behaves like a regular USB 2.0 hub.
Generally speaking, each of these two hubs operates independently of the other. Each hub has its own ports, and each of these ports operates independently. In particular, if the parameters of a port are changed on one of these parallel hubs, this has no effect on the other hub's ports.
Another conclusion is that if "lsusb -t" shows that a device is connected to the computer through the SuperSpeed root hub, it operates as a SuperSpeed device. In other words, its data rate is 5 Gbit/s or more. Likewise, if this device is connected through the USB 2.0 root hub, its data rate is 480 Mbit/s or less.
SuperSpeed and PORT_POWER
Recall that what hubpower actually did was to change the port's PORT_POWER attribute.
But a SuperSpeed hub consists of two hubs in parallel. Each of these two hubs has its own independent PORT_POWER attribute for each port. So when should the hub turn off the VBUS power? Each physical power switch depends on two PORT_POWER attributes, one from each of the parallel hubs.
Table 10-2 of the USB 3.0 specification gives the truth table for whether the hub should enable the power supply or not. It can be summarized as follows: If the hub is behaves as a USB 2.0 hub only (e.g. connected to a computer that doesn't support SuperSpeed), it follows the USB 2.0's PORT_POWER. If it's connected as a SuperSpeed hub (or both parallel hubs are connected), VBUS is turned off only if both PORT_POWER are zero.
If that sounded complicated, that was actually the easy part: The SuperSpeed part of the hub has a different internal state machine. That's quite expected, because the link training is done differently. But this state machine has three different states (instead of one, like USB 2.0) that are eligible when PORT_POWER is zero:
- DSPORT.Powered-off, which means that the port is completely inactive
- DSPORT.Powered-off-detect, which means that the port attempts to detect a SuperSpeed link partner
- DSPORT.Powered-off-reset, which means that the port performs a warm reset to the link partner (more on warm reset below).
The purpose of the two last states is to ensure that if a SuperSpeed device that has its own power supply is connected to the port, the connection will not fall back to USB 2.0. This would happen because the device would not have any way to recognize that it's connected to a SuperSpeed port.
So what did we learn from this? Mainly that resetting a device by changing PORT_POWER on a SuperSpeed hub is not as simple as with a USB 2.0 hub.
SuperSpeed: warm reset and hot reset
USB 2.0 has one simple way to reset the device: Both wires are connected by the hub to ground (SE0) for 10 ms. SuperSpeed devices, on the other hand, have two ways to reset a device: warm reset and hot reset. These should not be confused with PowerOn Reset and Inband Reset, which are terms that are used to define the reason for the reset. Inband Reset means that the reset occurs because of a request from the host. This can result in a warm reset or a hot reset, depending on the request's type (more on this just below).
It's important to distinguish between a warm reset and a hot reset: In particular, a warm reset involves stopping data stream and bringing it up from the beginning. A hot reset is sent on the data stream itself, and keeps this data stream running. The hot reset is hence much faster, but if there is a problem with the data stream that can be fixed by restarting it, a warm reset is required. An example to such a problem is if there are bit errors on the physical layer. Taking down the bitstream and start over again may fix this, possibly because it can correct a suboptimal tuning of the equalizer.
Recall that usbreset performs a ioctl() with USBDEVFS_RESET. Among all other things that happen, this changes PORT_RESET to zero, regardless of the device's USB version (as of Linux kernel v5.16).
According to the USB 3.0 specification, section 7.4.2, a PORT_RESET request results in a Hot Reset. This means that if the data stream is active, it is not torn down, but the reset command is sent on this data stream. This spec also defines BH_PORT_RESET (feature number 28), which forces a warm reset (unless the port is disabled). This reset is more fundamental: It brings down the data stream, and restarts the procedure for bringing it up again (by virtue of LFPS signaling). The important difference is that if the data stream needs a restart, BH_PORT_RESET will do it, but PORT_RESET won't.
A connection of a new SuperSpeed device involves a Warm Reset. So it's a shame that there's apparently no tool that can do the trick with PORT_POWER on a SuperSpeed port. As mentioned above, uhubctl can do this technically, but the device ends up in a messy state.
Leftover jots
These are random pieces of information that may be useful, but has no proper context.
- The codes of commands like PORT_POWER are listed as Hub class feature selectors in table 11-17 of the USB 2.0 specification, and table 10-8 of the USB 3.0 specification.
- The function in the kernel that enables PORT_RESET is hub_port_reset(). But to actually reinitialize a port, there's usb_reset_device(), which also prepares the owning driver for the reset, and then makes a function call to usb_reset_and_verify_device(). All of these functions are defined in drivers/usb/core/hub.c, and only usb_reset_device() is exported.
- In drivers/usb/core/devio.c, usb_reset_device() is called by proc_resetdevice(), which can be triggered by a USBDEVFS_RESET ioctl() on the relevant device file. This is what usbreset does (see above).
- This is equivalent to libusb's op_reset_device() in linux_usbfs.c (it uses IOCTL_USBFS_RESET instead, but it's equal to USBDEVFS_RESET, both are 20, compare with the kernel's include/uapi/linux/usbdevice_fs.h). libusb's function also unclaims the interfaces.proc_resetdevice() will politely refuse to do so if any of the device's interfaces has been claimed.
- In hub.c, port_event() is called by hub_event(). The former is the one that detects changes in the port's bits. hub_event() is a work item, which is kicked off by kick_hub_wq() (in particular by hub_irq(), which "fires on port status changes and various faults" in response to status change notifications that arrive on the hub's IN endpoint, which is designated for this purpose).