Einführung
In einer perfekten Welt benehmen sich USB-Geräte und USB-Hubs vorbildlich und schaffen es, jedes auftretende Problem selbst zu beheben. In der Realität passieren jedoch alle möglichen seltsamen Dinge, und man muss etwas dagegen unternehmen. Die übliche Lösung ist, den USB-Stecker herauszuziehen und das Gerät wieder anzuschließen. Aber was, wenn das automatisch passieren muss?
Die radikalste Lösung wird auf einer anderen Webseite vorgestellt: Sie fährt die Treiber des USB-Hubs herunter und startet sie neu. Der Effekt entspricht fast dem Abziehen und Wiederanschließen aller USB-Geräte am Computer. Das ist der schwerste Hammer – und manchmal notwendig.
Diese Seite stellt zwei Methoden vor, um ein bestimmtes USB-Gerät zurückzusetzen. Ich zeige zunächst, wie man jede Methode ausführt, mit ein paar kurzen Erläuterungen. Danach gehe ich auf viele technische Details ein.
Die gesamte Betrachtung beschränkt sich hier auf Linux, obwohl die zweite vorgestellte Methode wahrscheinlich auch auf anderen Plattformen möglich ist.
Viele Informationen hier stammen aus der Universal Serial Bus Specification Revision 2.0. Die Datei heißt üblicherweise usb_20.pdf.
Methode Nr. 1: Den Linux-Kernel bitten, das Gerät zurückzusetzen
Für diesen Zweck gibt es mehrere Werkzeuge. Das ausgefeilteste ist Teil von usbutils. Laden Sie usbreset.c herunter und übersetzen Sie es mit gcc:
$ gcc -O3 -Wall -g usbreset.c -o usbreset
Und dann:
$ ./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
Als Reaktion auf die Resets erscheint Folgendes im Kernel-Log:
usb 1-6: reset full-speed USB device number 4 using xhci_hcd
Was diese Sitzung zeigt:
- Ohne Argumente antwortet usbreset mit einer Liste der USB-Geräte. Die gleichen Informationen erhält man mit lsusb.
- Man muss root sein, um ein Gerät tatsächlich zurückzusetzen (daher sudo).
- Das Gerät kann über seine Busadresse ausgewählt werden (Busnummer und Gerätenummer). Beachten Sie, dass sich die Busadresse bei jedem Anschließen des Geräts ändert.
- Das Gerät kann auch über seine Vendor-/Product-IDs ausgewählt werden. Das ist die bevorzugte Methode, wenn es nur ein USB-Gerät dieser Art gibt.
- Das Gerät hat seine Adresse am USB-Bus nicht geändert (es wurde durch den Reset nicht neu enumeriert).
Was tut usbreset also? Es läuft im Endeffekt auf diesen Befehl auf der Gerätedatei des USB-Geräts hinaus:
ioctl(fd, USBDEVFS_RESET, 0)
Das führt zu einem Funktionsaufruf an usb_reset_device() des Kernels, das viel mehr tut, als das Gerät nur zurückzusetzen. Die Idee ist, den gesamten Ablauf reibungslos zu gestalten: Die Funktion benachrichtigt den Treiber des Geräts vor und nach dem Reset, sofern dies gewünscht ist. Sie löst den Treiber vor dem Reset und bindet ihn danach wieder an. Die Konfiguration des Geräts wird nach dem Reset ebenfalls geladen. Ohne das wüsste das Gerät seine Busadresse nicht und wäre für keinen Datenaustausch bereit.
Es ist also fast wie ein erneutes Anschließen des Geräts, aber ohne dass es sich ausweisen muss (weil die Informationen bereits bekannt sind) und ohne dass ihm eine neue Adresse zugewiesen wird.
Bei USB 3.x handelt es sich dabei um einen Hot Reset – die weniger effiziente Variante. Weiter unten mehr zum Hot Reset.
Methode Nr. 2: Den Stromzustand des USB-Ports umschalten
Für diesen Zweck gibt es ebenfalls mehrere Werkzeuge. Ich zeige die Methode anhand von hubpower. Laden Sie hubpower.c herunter und übersetzen Sie es:
$ gcc -O3 -Wall -g hubpower.c -o hubpower
Die Verwendung dieses Werkzeugs ist etwas kniffliger, denn die Befehle werden nicht an das USB-Gerät selbst geschickt. Stattdessen gehen die Anforderungen an den USB-Hub, an den das USB-Gerät angeschlossen ist.
Der erste Schritt ist also herauszufinden, um welchen Hub es sich handelt. Zuerst ermitteln wir die Adresse des USB-Geräts:
$ 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
Das Gerät befindet sich also auf Bus Nummer 1 und hat die Gerätenummer 4. Beachten Sie, dass sich die Gerätenummer bei jeder Enumeration des USB-Geräts ändert.
An welchen Hub ist das USB-Gerät also angeschlossen? Und an welchen 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
Gerät Nummer 4 ist also an Port 6 angeschlossen. Der Hub hat die Busnummer 1 und die Gerätenummer 1 (es ist der USB-Controller des Mainboards, der als Root-Hub fungiert).
Schauen wir uns an, was hubpower dazu sagt:
$ 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
Beachten Sie, dass der Teil »1:1« die Busadresse des Hubs ist. Wenn es sich um einen externen Hub handelt, ändert sich diese Adresse jedes Mal, wenn der Hub an den Computer angeschlossen wird.
Die Nummern der Ports ändern sich dagegen nie (solange das Gerät am selben physischen Port angeschlossen ist).
Nun, da wir die Portnummer des Geräts kennen, setzen wir es zurück:
$ 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
Nach dem ersten Befehl wird das Gerät im Kernel-Log als getrennt gemeldet:
usb 1-6: USB disconnect, device number 4
Nach dem zweiten Befehl verhält sich der Computer so, als wäre das Gerät physisch angeschlossen worden:
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
[ ... ]
Wegen dieser erneuten Enumeration wird dem Gerät eine neue Busadresse zugewiesen:
$ 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
Handelt es sich also um ein physisches Abziehen des USB-Geräts und ein erneutes Anschließen? Die Antwort lautet: vielleicht. In den meisten Fällen schalten die hubpower-Befehle die Stromversorgung des USB-Geräts nicht wirklich ab. Das Gerät erhält also weiterhin seine VBUS-Spannung (5 V). Nur sehr wenige USB-Hubs schalten die Spannung tatsächlich ab. Mehr dazu weiter unten.
Bei der überwiegenden Mehrheit der Hubs versetzt der erste Befehl (»power off«) den Port nur in einen Zustand, in dem er das Gerät ignoriert. Der zweite Befehl (»power on«) bringt den Port in seinen natürlichen Zustand zurück. Das Ergebnis ist, dass das Gerät erkannt wird und seine Initialisierungsprozedur beginnt. Unter anderem wird das Gerät zurückgesetzt und enumeriert, und sein Treiber initialisiert es.
Ein physisches Abtrennen ist daher meist wirkungsvoller als diese Befehle, insbesondere wenn das Gerät seine Stromversorgung über den USB-Stecker bezieht. Wenn der Hub die VBUS-Spannung aber wirklich abschaltet, ist hubpower genauso wirkungsvoll.
Beachten Sie, dass die Befehle in diesem Beispiel an den USB-Controller des Mainboards (den Root-Hub) geschickt wurden. Der Grund ist, dass das USB-Gerät direkt am Computer angeschlossen war. Ist das Gerät über einen externen Hub an den Computer angeschlossen, sollten die Befehle an diesen externen Hub geschickt werden. In beiden Fällen wird hubpower auf dieselbe Weise verwendet.
hubpower unterstützt USB 3.0 (SuperSpeed) leider nicht. Mehr dazu weiter unten.
Unterschiede zwischen den beiden Methoden
Worin unterscheidet sich die zweite Methode (mit hubpower) nun von der ersten (mit usbreset)? Zur Lösung eines Problems mit einem Gerät tun beide Methoden im Wesentlichen dasselbe: Sie senden einen Reset-Befehl. Die Art und Weise, wie das geschieht, ist grundlegend verschieden, aber es ist immer noch dieser Reset-Befehl, der das Problem wahrscheinlich löst – wenn überhaupt einer es löst.
Es gibt jedoch einige wichtige Unterschiede:
- hubpower funktioniert auch dann, wenn das Gerät am Bus nicht erkannt wird. Zum Beispiel, wenn der Computer mehrmals versucht hat, das Gerät zu enumerieren, und dann aufgegeben hat. In diesem Fall kann das Gerät physisch am Computer angeschlossen sein, aber usbreset ist nutzlos, weil das Gerät keine Busadresse hat.
- Nur hubpower kann helfen, wenn der USB-Port deaktiviert wird: Manchmal ignoriert der Computer einen USB-Port nach mehreren Fehlern vollständig. hubpower sollte das lösen.
- hubpower funktioniert nicht mit SuperSpeed (USB 3.x). Es ist vermutlich nur eine Frage der Implementierung. Mehr zu SuperSpeed weiter unten.
- hubpower kann unter Umständen auch die Stromversorgung des USB-Geräts steuern (meistens geschieht das aber nicht). Das ist ein Vorteil, wenn das Gerät zur Problemlösung von der Spannung getrennt und wieder angeschlossen werden muss.
- hubpower ist schwieriger zu handhaben: Man muss herausfinden, welcher Port des Hubs mit dem USB-Gerät verbunden ist. Außerdem die Busadresse des Hubs.
Elektrische Geräte steuern (?)
Auch wenn das Hauptthema dieser Seite die Behebung von USB-Problemen ist, gibt es auch einen interessanten Nebeneffekt: Es ist manchmal möglich, die 5-V-Stromversorgung des USB-Ports zu steuern. Mit anderen Worten: Ein einfacher und billiger USB-Hub lässt sich dazu verwenden, eine Stromversorgung mit einer Belastbarkeit von 2,5 Watt ein- und auszuschalten.
Das ist mehr als genug, um ein elektromechanisches Relais anzusteuern. Es muss also nur eine Komponente hinzugefügt werden, um ein elektrisches Gerät zu schalten, das mit 110 V / 220 V betrieben wird. Es ist allerdings ratsam, eine einfache Freilaufdiode einzubauen, um den USB-Hub vor Schäden zu schützen. Und das war es auch schon.
Leider ist die Fähigkeit zur Steuerung der Stromversorgung optional: Laut Abschnitt 11.11 der USB-2.0-Spezifikation darf ein Hub über Schalter verfügen, die die 5-V-Stromversorgung eines Ports abschalten, wenn sich der Port im Powered-Off-Zustand befindet. Alternativ kann ein Stromversorgungsschalter auch die Stromversorgung mehrerer Ports steuern (»ganged power switching«). Der Zweck der Steuerung der Stromversorgung besteht in erster Linie darin, USB-Geräte abzuschalten, die zu viel Strom ziehen, damit die übrigen Ports normal weiterarbeiten können.
Wie bereits erwähnt, ist die physische Steuerung der Stromversorgung optional. Was hubpower tatsächlich tut, ist, den Wert des Attributs PORT_POWER des Ports zu ändern (in den USB-Spezifikationen wird das als »Feature« bezeichnet). Diese Änderung betrifft zwei verschiedene Aspekte des Hub-Ports:
- Verpflichtend: der logische Zustand des Ports. Wenn PORT_POWER null ist, kann sich der Port nur im Powered-Off-Zustand (oder Not Configured) befinden. Mit anderen Worten: Der Hub muss sich so verhalten, als läge keine Spannung am Port an, und deshalb ein angeschlossenes Gerät ignorieren, falls eines vorhanden ist.
- Optional: das Vorhandensein von VBUS (der 5-V-Spannung) an den Stromversorgungsleitungen des Ports. Wenn der Hub dieses Feature nicht unterstützt, kann die Spannung auch dann anliegen, wenn PORT_POWER null ist.
PORT_POWER wird weiter unten genauer erklärt.
Steuert mein Hub die Spannung?
Woher wissen Sie, ob Ihr Hub die Spannung tatsächlich abschaltet? Der einzige Weg, es sicher herauszufinden, ist ein Test. Schließen Sie etwas an, das kein USB-Gerät ist, aber Strom aus dem USB-Port bezieht. Echte USB-Geräte können dabei verwirren. Eine optische Maus beispielsweise schaltet ihre LED normalerweise als Reaktion auf den Power-Off-Befehl aus, auch wenn die 5-V-Spannung weiterhin anliegt.
Jeder Hub gibt in den Informationen, die mit »lsusb -v« sichtbar sind (im Hub-Deskriptor, definiert in Abschnitt 11.23.2.1 der Spezifikation), an, ob und in welcher Abstufung er die Spannung steuert. Der Hub kann angeben, dass er die Spannung nicht steuert, dass er die Spannung für Portgruppen steuert (»ganged power switching«) oder dass er die Spannung jedes Ports einzeln steuert: Laut Abschnitt 11.11 der USB-2.0-Spezifikation kann ein Hub mit Stromversorgungsschaltern die Stromversorgung aller Ports als Gruppe (Gang), für jeden Port einzeln oder in einer beliebigen Zahl von Gruppen (Gangs) mit einem oder mehreren Ports schalten.
Diese Information ist jedoch nicht zuverlässig. Ich bin schon auf mehrere Hubs gestoßen, die angegeben haben, die Spannung zu steuern, aber keiner hat es getan.
Hier ist ein Beispiel für die Ausgabe von »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
Sieht vielversprechend aus, nicht wahr? In Wirklichkeit steuert dieser Hub die Spannungen überhaupt nicht.
Im Gegensatz dazu ein ganz gewöhnlicher Mainboard-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
Dieser Hub gibt also offen zu, keine Spannungssteuerung zu unterstützen.
Beachten Sie, dass es neben den Angaben zur Stromversorgungsschaltung auch Statusinformationen zu jedem Port gibt, die als »Hub Port Status« ausgegeben werden. Diese Statusbits sind in Tabelle 11-21 der USB-2.0-Spezifikation (Abschnitt 11.24.2.7.1) definiert. Genau diese Informationen ruft hubpower ab.
Das Werkzeug uhubctl
Die Idee, mit einem USB-Hub ein Relais zu steuern, ist die Motivation für viele Initiativen. Ein Blick auf uhubctl lohnt sich, vor allem weil dieses Projekt eine Liste von USB-Hubs pflegt, die die Spannung steuern.
Dieses Werkzeug ist eindeutig nur für die Spannungssteuerung gedacht, nicht zur Lösung von Problemen mit einem USB-Gerät. Beispielsweise ignoriert uhubctl standardmäßig Hubs, die nicht angeben, die Spannungen für jeden Port einzeln steuern zu können.
Die folgenden Befehle entsprechen den obigen hubpower-Befehlen:
$ 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
Die Befehlssyntax ist deutlich unbequemer. Sie wird weiter unten kurz erklärt.
uhubctl basiert auf libusb und funktioniert daher auch mit anderen Betriebssystemen. hubpower greift dagegen direkt auf die Gerätedatei des Hubs unter /dev/bus/usb/ (bzw. /proc/bus/usb/) zu, was nur unter Linux funktioniert.
uhubctl unterstützt zwar auch USB 3.x (siehe Git-Commit und seine Fortsetzung). Allerdings scheint sich niemand darum gekümmert zu haben, was passiert, wenn ein echtes USB-Gerät am Hub hängt: Meine Versuche, ein SuperSpeed-Gerät zurückzusetzen, führten zu seltsamen Ergebnissen: Nach dem Wiedereinschalten der Spannung verharrte das Gerät im Polling-Zustand und wurde nicht enumeriert. Außerdem blieb »lsusb -v« hängen, solange die Spannung abgeschaltet war. Da ist also einiges schiefgelaufen.
Das sind die Befehle, um an einem SuperSpeed-Port die Stromversorgung abzuschalten und wieder einzuschalten. Mehr zu SuperSpeed weiter unten.
# ./uhubctl -f -e -l 2 -p 4 -a 0 # ./uhubctl -f -e -l 2 -p 4 -a 1
- -f bedeutet, mit einem Hub zu arbeiten, auch wenn dieser meldet, dass er keine Spannungssteuerung für jeden einzelnen Port unterstützt.
- -e steht für »exact position« (exakte Position), d. h. ein SuperSpeed-Port wird wie zwei Hubs gezählt.
- -l 2 bedeutet: der zweite Hub (der erste Hub war der USB-2.0-Hub in meinem Computer).
- -p 4 bedeutet: Port Nummer vier.
- -a 0 bedeutet: Stromversorgung ausschalten (Aktion #0).
- -a 1 bedeutet: Stromversorgung einschalten (Aktion #1).
PORT_POWER erklärt
Wenn vom Host ein Befehl eintrifft, PORT_POWER auf null zu ändern, geht der Port bedingungslos in den Powered-Off-Zustand über. Das gilt auch, wenn der Hub das Gerät weiterhin mit 5 V versorgt. Es gibt weitere Gründe, warum PORT_POWER null wird, insbesondere eine Überstrombedingung am Port (das Gerät zieht zu viel Strom).
PORT_POWER kann nur durch einen Befehl des Hosts wieder auf »1« gesetzt werden. Dadurch geht der Port in den Disconnected-Zustand über. USB-Ports, an denen nichts angeschlossen ist, befinden sich normalerweise in diesem Zustand. Wenn ein Gerät am Port erkannt wird, wechselt der Zustand nach einer kurzen Verzögerung zu Disabled. Ist das Gerät bereits angeschlossen und PORT_POWER wechselt auf »1«, geschieht das sofort.
Von diesem Zustand aus führt der einzige Weg zur Aktivierung des Geräts über das Zurücksetzen des Ports (mit PORT_RESET, siehe unten). Nur der Host kann das tun. Der Hub kann also nur den Host benachrichtigen, dass ein angeschlossenes Gerät seine Aufmerksamkeit benötigt.
Hier kommt eines der Statusbits des Ports ins Spiel: PORT_CONNECTION. Dieses Bit muss null sein, wenn sich der Port im Powered-Off- oder im Disconnected-Zustand befindet. PORT_CONNECTION wechselt auf »1«, wenn der Port vom Disconnected- in den Disabled-Zustand übergeht. Eine Änderung von PORT_CONNECTION erzeugt ein Hub-Ereignis, sodass der Treiber benachrichtigt wird (d. h. in hub.c des Kernels wird port_event() mit gesetztem USB_PORT_FEAT_C_CONNECTION aufgerufen). Der Treiber reagiert darauf, indem er den Port zurücksetzt (er setzt das PORT_RESET-Bit des Ports auf »1«) und das angeschlossene Gerät enumeriert.
Das alles bezieht sich auf USB 2.0. Abbildung 11-10 der USB-2.0-Spezifikation zeigt, wie ein Hub-Port seine Zustände wechselt.
Direkte Steuerung von PORT_RESET und PORT_ENABLE
hubpower kann auch zwei weitere Attribute direkt ändern: PORT_RESET und PORT_ENABLE. Ich habe diese Fähigkeit in meinem eigenen Fork des Werkzeugs ergänzt. Beachten Sie jedoch, dass man damit nur eines bewerkstelligen kann: Man kann das USB-Gerät absichtlich in einen Fehlerzustand bringen (und damit den Computer dazu bringen, das Gerät zur Korrektur zurückzusetzen). Nun ins Detail:
Um einen Port-Reset gemäß Abschnitt 11.5.1.5 der USB-2.0-Spezifikation einzuleiten, muss der Host PORT_RESET auf »1« setzen. Der Hub setzt PORT_RESET nach Abschluss des Resets wieder auf »0«. Der Hub beginnt einen Port-Reset niemals aus eigenem Antrieb, und der Host darf in dieses Attribut nicht »0« schreiben (Abschnitt 11.24.2.7.1.5).
Der Hub ignoriert PORT_RESET, wenn sich der Port im Powered-Off- oder im Disconnected-Zustand befindet.
Was PORT_ENABLE betrifft: Wenn dieses Attribut auf »0« wechselt, geht der Port in den Disabled-Zustand über. Dies kann aufgrund einer Anforderung des Hosts geschehen, aber auch durch das Abtrennen des USB-Geräts, einen Powered-Off-Zustand des Ports oder einen Fehler während des Reset-Vorgangs.
PORT_ENABLE kann nur als Ergebnis einer Port-Reset-Anforderung des Hosts auf »1« wechseln (Abschnitt 11.24.2.7.1.2 der USB-2.0-Spezifikation).
Der Host darf PORT_RESET daher nicht auf »0« oder PORT_ENABLE auf »1« ändern. Ein solcher Versuch führt zu einer Fehlerantwort des Hubs (der entsprechende ioctl()-Aufruf liefert einen Fehlerstatus zurück).
hubpower kann einen Reset direkt anfordern, indem es das PORT_RESET des Ports auf »1« setzt. Dadurch wird das USB-Gerät zurückgesetzt und verliert dabei seine Busadresse. Das Gerät ist danach nicht mehr erreichbar.
Dieser Befehl wird direkt an den Hub geschickt, sodass der Hub-Treiber im Linux-Kernel nichts davon erfährt. Tatsächlich bemerkt der Computer erst dann, dass sich etwas geändert hat, wenn er versucht, auf das USB-Gerät zuzugreifen. Was als Nächstes passiert, hängt vom Treiber des Geräts ab. Das Gerät wird so behandelt, als hätte es einen Hardware-Fehler. Entsprechend wird eine Maßnahme zur Fehlerkorrektur ergriffen. Höchstwahrscheinlich beinhaltet diese einen Reset.
Ein direkter Reset mit hubpower wird also wahrscheinlich das gewünschte Ergebnis erzielen, aber mit viel unnötigem Drama. PORT_POWER erledigt das eleganter. Der einzige mögliche Vorteil eines direkten PORT_RESET ist, dass der Treiber dann vielleicht sagt: »Hey, mit diesem Gerät stimmt wirklich etwas nicht, lass uns etwas Drastisches tun, um es zu beheben.« Und das könnte helfen.
Was die Manipulation von PORT_ENABLE betrifft, passiert dasselbe: Das Gerät verschwindet plötzlich. Doch auch in diesem Fall merkt der Computer nicht sofort, dass etwas passiert ist: Laut Abschnitt 11.24.2.7.2.2 der USB-2.0-Spezifikation wird eine Änderungsmeldung für PORT_ENABLE (d. h. C_PORT_ENABLE) nur dann ausgelöst, wenn der Port aufgrund eines Fehlers auf dem Link deaktiviert wird. Die Spezifikation sagt ausdrücklich, dass diese Meldung aus keinem anderen Grund erfolgt.
Das Ändern von PORT_ENABLE auf null hat also in etwa dieselbe Wirkung wie das direkte Ändern von PORT_RESET. Mit einem Nachteil: Laut Abschnitt 10.14.2.6.1 der USB-3.0-Spezifikation wird PORT_ENABLE »von SuperSpeed-Hubs nicht unterstützt«.
SuperSpeed (USB 3.x)
Zuallererst: Wenn Sie das hier lesen, weil Sie ein Problem mit einem USB-3.x-Gerät haben, fragen Sie sich, ob Sie die Datenrate, die USB 3.x bietet, wirklich benötigen. Ist die Antwort nein, versuchen Sie, das Gerät über einen USB-Hub ohne USB-3.x-Unterstützung (oder mit einem kurzen USB-2.0-Kabel) an den Computer anzuschließen. Das allein könnte das Problem lösen.
SuperSpeed-USB (also USB 3.x) existiert parallel zu USB 2.0. Jedes SuperSpeed-Gerät besteht praktisch aus zwei Geräten: einem separaten Gerät für SuperSpeed und einem weiteren separaten Gerät für USB 2.0. Jede der beiden USB-Versionen nutzt eigene Adern des USB-Kabels. Sie sind sowohl elektrisch als auch konzeptionell voneinander unabhängig.
Die USB-Spezifikationen verlangen, dass jedes SuperSpeed-Gerät aus diesen beiden Geräten besteht, obwohl das praktisch nicht nötig wäre. Mit anderen Worten: Ein SuperSpeed-Gerät ohne USB-2.0-Unterstützung funktioniert einwandfrei, wenn es an einen SuperSpeed-Port angeschlossen wird.
Wenn ein SuperSpeed-Gerät an einen SuperSpeed-Port angeschlossen wird, wird zuerst versucht, über die SuperSpeed-Schnittstelle eine Verbindung herzustellen. Schlägt das fehl, wird ein Verbindungsversuch über USB 2.0 unternommen. In der Praxis (und laut Spezifikation) verbindet sich ein USB-Gerät nie gleichzeitig über beide Versionen. Das ist aber möglich und führt dazu, dass sich das USB-Gerät wie zwei getrennte Geräte verhält.
Ein SuperSpeed-Hub besteht aus zwei parallelen Hubs: einem für USB 2.0 und einem für SuperSpeed. Wenn Sie einen externen SuperSpeed-USB-Hub an einen Computer anschließen, werden dem System zwei Hubs hinzugefügt. Sie erscheinen als zwei getrennte Geräte. Ein normales USB-Gerät darf nicht beide Versionen parallel nutzen, ein Hub muss das jedoch.
Wird ein SuperSpeed-Hub an einen USB-2.0-Port angeschlossen, verhält er sich wie ein gewöhnlicher USB-2.0-Hub.
Allgemein arbeitet jeder dieser beiden Hubs unabhängig vom anderen. Jeder Hub hat seine eigenen Ports, und jeder dieser Ports arbeitet unabhängig. Insbesondere hat eine Änderung der Parameter eines Ports an einem dieser parallelen Hubs keine Auswirkung auf die Ports des anderen Hubs.
Daraus folgt auch: Wenn »lsusb -t« zeigt, dass ein Gerät über den SuperSpeed-Root-Hub mit dem Computer verbunden ist, arbeitet es als SuperSpeed-Gerät. Seine Datenrate beträgt also 5 Gbit/s oder mehr. Ist das Gerät dagegen über den USB-2.0-Root-Hub verbunden, beträgt seine Datenrate 480 Mbit/s oder weniger.
SuperSpeed und PORT_POWER
Zur Erinnerung: hubpower hat im Grunde nur das Attribut PORT_POWER des Ports geändert.
Aber ein SuperSpeed-Hub besteht aus zwei parallelen Hubs. Jeder dieser beiden Hubs hat für jeden Port ein eigenes, unabhängiges PORT_POWER-Attribut. Wann soll der Hub also die VBUS-Spannung abschalten? Jeder physische Stromversorgungsschalter hängt von zwei PORT_POWER-Attributen ab, einem von jedem der parallelen Hubs.
Tabelle 10-2 der USB-3.0-Spezifikation enthält die Wahrheitstabelle dafür, ob der Hub die Stromversorgung einschalten soll oder nicht. Sie lässt sich wie folgt zusammenfassen: Verhält sich der Hub nur als USB-2.0-Hub (z. B. weil er an einen Computer angeschlossen ist, der kein SuperSpeed unterstützt), folgt er dem PORT_POWER von USB 2.0. Ist er als SuperSpeed-Hub angeschlossen (oder sind beide parallelen Hubs verbunden), wird VBUS nur abgeschaltet, wenn beide PORT_POWER null sind.
Falls das kompliziert klang: Das war erst der einfache Teil. Der SuperSpeed-Teil des Hubs besitzt eine andere interne Zustandsmaschine (state machine). Das ist nicht weiter überraschend, denn das Link-Training läuft anders ab. Diese Zustandsmaschine hat jedoch drei verschiedene Zustände (statt wie bei USB 2.0 nur einem), die bei PORT_POWER null vorgesehen sind:
- DSPORT.Powered-off – der Port ist also vollständig inaktiv.
- DSPORT.Powered-off-detect – der Port versucht, einen SuperSpeed-Linkpartner zu erkennen.
- DSPORT.Powered-off-reset – der Port führt einen Warm Reset am Linkpartner durch (mehr zum Warm Reset weiter unten).
Der Zweck der beiden letzten Zustände ist es, sicherzustellen, dass die Verbindung nicht auf USB 2.0 zurückfällt, wenn ein SuperSpeed-Gerät mit eigener Stromversorgung an den Port angeschlossen wird. Das würde nämlich geschehen, weil das Gerät keine Möglichkeit hätte zu erkennen, dass es an einem SuperSpeed-Port hängt.
Was lernen wir daraus? Vor allem, dass das Zurücksetzen eines Geräts durch Ändern von PORT_POWER an einem SuperSpeed-Hub nicht so einfach ist wie an einem USB-2.0-Hub.
SuperSpeed: Warm Reset und Hot Reset
USB 2.0 hat eine einfache Möglichkeit, das Gerät zurückzusetzen: Der Hub verbindet beide Leitungen für 10 ms mit Masse (SE0). SuperSpeed-Geräte haben dagegen zwei Möglichkeiten: Warm Reset und Hot Reset. Diese sollte man nicht mit PowerOn Reset und Inband Reset verwechseln; diese Begriffe bezeichnen den Grund für den Reset. Inband Reset bedeutet, dass der Reset aufgrund einer Anforderung des Hosts erfolgt. Je nach Art der Anforderung kann das zu einem Warm Reset oder einem Hot Reset führen (gleich mehr dazu).
Es ist wichtig, zwischen Warm Reset und Hot Reset zu unterscheiden: Ein Warm Reset bedeutet insbesondere, dass der Datenstrom angehalten und von vorn aufgebaut wird. Ein Hot Reset wird auf dem laufenden Datenstrom selbst gesendet und lässt diesen Datenstrom weiterlaufen. Der Hot Reset ist also viel schneller. Wenn es jedoch ein Problem mit dem Datenstrom gibt, das durch einen Neustart behoben werden kann, ist ein Warm Reset erforderlich. Ein Beispiel für ein solches Problem sind Bitfehler auf der physischen Schicht. Das Herunterfahren des Bitstroms (bitstream) und ein erneuter Aufbau können das beheben, möglicherweise weil dabei eine nicht optimale Abstimmung des Entzerrers (equalizer) korrigiert wird.
Zur Erinnerung: usbreset führt ein ioctl() mit USBDEVFS_RESET aus. Unter anderem wird dabei PORT_RESET auf null gesetzt, unabhängig von der USB-Version des Geräts (Stand Linux-Kernel v5.16).
Laut Abschnitt 7.4.2 der USB-3.0-Spezifikation führt eine PORT_RESET-Anforderung zu einem Hot Reset. Das bedeutet: Wenn der Datenstrom aktiv ist, wird er nicht abgebaut, sondern der Reset-Befehl wird auf diesem Datenstrom gesendet. Die Spezifikation definiert außerdem BH_PORT_RESET (Feature-Nummer 28), das einen Warm Reset erzwingt (sofern der Port nicht deaktiviert ist). Dieser Reset ist grundlegender: Er fährt den Datenstrom herunter und beginnt die Prozedur für den erneuten Aufbau von vorn (mittels LFPS-Signalisierung). Der wichtige Unterschied ist: Wenn der Datenstrom neu gestartet werden muss, schafft das BH_PORT_RESET, aber PORT_RESET nicht.
Das Anschließen eines neuen SuperSpeed-Geräts ist mit einem Warm Reset verbunden. Es ist daher schade, dass es offenbar kein Werkzeug gibt, das den PORT_POWER-Trick an einem SuperSpeed-Port sauber hinbekommt. Wie oben erwähnt, kann uhubctl das technisch zwar erledigen, aber das Gerät landet danach in einem unschönen Zustand.
Verstreute Notizen
Das sind verstreute Informationen, die nützlich sein können, aber in keinen rechten Zusammenhang passen.
- Die Codes von Befehlen wie PORT_POWER sind in Tabelle 11-17 der USB-2.0-Spezifikation und Tabelle 10-8 der USB-3.0-Spezifikation als Feature-Selektoren der Hub-Klasse (Hub Class Feature Selectors) aufgelistet.
- Die Funktion im Kernel, die PORT_RESET auslöst, heißt hub_port_reset(). Zur tatsächlichen Neuinitialisierung eines Ports gibt es jedoch usb_reset_device(). Diese Funktion bereitet auch den zugehörigen Treiber auf den Reset vor und ruft dann usb_reset_and_verify_device() auf. Alle diese Funktionen sind in drivers/usb/core/hub.c definiert; exportiert wird nur usb_reset_device().
- In drivers/usb/core/devio.c wird usb_reset_device() von proc_resetdevice() aufgerufen. Dieses lässt sich durch ein USBDEVFS_RESET-ioctl() auf der betreffenden Gerätedatei auslösen. Genau das macht usbreset (siehe oben).
- Das entspricht der Funktion op_reset_device() von libusb in linux_usbfs.c (dort wird stattdessen IOCTL_USBFS_RESET verwendet, aber das ist gleichbedeutend mit USBDEVFS_RESET; beide haben den Wert 20, vergleiche die Kernel-Datei include/uapi/linux/usbdevice_fs.h). Die libusb-Funktion gibt außerdem die Interfaces frei. proc_resetdevice() verweigert das freundlicherweise, wenn eines der Interfaces des Geräts beansprucht wurde.
- In hub.c wird port_event() von hub_event() aufgerufen. port_event() erkennt Änderungen in den Bits des Ports. hub_event() ist ein Arbeitsobjekt (work item), das von kick_hub_wq() angestoßen wird (insbesondere von hub_irq(), das »bei Port-Statusänderungen und verschiedenen Fehlern« auslöst, als Reaktion auf Statusänderungsmeldungen, die am IN-Endpunkt des Hubs eintreffen, der für diesen Zweck vorgesehen ist).