01signal.com

Réinitialiser un périphérique USB sous Linux (et éventuellement piloter son alimentation)

Introduction

Dans un monde parfait, les périphériques USB et les hubs se comportent bien et parviennent à résoudre les problèmes qu'ils rencontrent. Dans la réalité, toutes sortes de choses bizarres arrivent, et il faut bien faire quelque chose. La solution habituelle consiste à débrancher le câble USB puis à rebrancher le périphérique. Mais que faire quand cette opération doit se faire automatiquement ?

La solution la plus radicale est présentée sur une autre page web : elle provoque l'arrêt et le redémarrage des pilotes du hub USB. L'effet est presque équivalent à débrancher puis rebrancher tous les périphériques USB de l'ordinateur. C'est le marteau le plus lourd, et il est parfois nécessaire.

Cette page présente deux méthodes pour réinitialiser un périphérique USB bien précis. Je commence par montrer comment mettre en œuvre chaque méthode, avec un minimum d'explications. Ensuite, j'entre dans beaucoup de détails techniques.

Toute la discussion est limitée à Linux, même si la seconde méthode présentée ici est probablement possible sur d'autres plateformes également.

Une grande partie des informations de cette page provient de la spécification Universal Serial Bus Revision 2.0. Le nom de fichier de ce document est en général usb_20.pdf.

Méthode n°1 : demander au noyau Linux de réinitialiser le périphérique

Plusieurs outils permettent de faire cela. L'outil le plus abouti fait partie d'usbutils. Téléchargez usbreset.c et compilez-le avec gcc :

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

Et ensuite :

$ ./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

À la suite de ces réinitialisations, on voit apparaître ceci dans le journal du noyau :

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

Ce que cette session démontre :

Alors, que fait exactement usbreset ? Tout se résume à cette commande sur le fichier représentant le périphérique USB :

ioctl(fd, USBDEVFS_RESET, 0)

Cela aboutit à un appel à la fonction usb_reset_device() du noyau, qui fait bien plus qu'un simple reset du périphérique. L'idée est de rendre tout le processus fluide : cette fonction informe le pilote du périphérique du reset avant et après, si celui-ci le demande. Elle détache le pilote avant le reset et le rattache ensuite. La configuration du périphérique est également rechargée après le reset. Sans cela, le périphérique ne connaîtrait pas son adresse de bus et ne serait pas prêt à échanger des données.

C'est donc presque comme si on rebranchait le périphérique, sauf qu'on ne lui demande pas de s'identifier (car l'information est déjà connue) et qu'on ne lui attribue pas de nouvelle adresse.

Pour l'USB 3.x, il s'agit d'un hot reset, ce qui est le type de reset le moins efficace. Plus de détails sur le hot reset plus bas.

Méthode n°2 : changer l'état d'alimentation du port USB

Il existe également plusieurs outils pour cela. Je vais démontrer cette méthode avec hubpower. Téléchargez hubpower.c et compilez-le :

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

Cet outil est un peu plus délicat à utiliser, car les commandes ne sont pas envoyées au périphérique USB lui-même. Les requêtes sont envoyées au hub USB auquel le périphérique est connecté.

La première étape consiste donc à trouver quel hub c'est. Commençons par trouver l'adresse du périphérique 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

Le périphérique se trouve donc sur le bus numéro 1 et le périphérique numéro 4. Notez que le numéro de périphérique change à chaque énumération du périphérique USB.

À quel hub le périphérique USB est-il connecté, alors ? Et à quel 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

Le périphérique numéro 4 est donc connecté sur le port n°6. Le numéro de bus du hub est 1 et son numéro de périphérique est 1 (c'est le contrôleur USB de la carte mère, qui fait office de hub racine).

Voyons ce que hubpower a à dire à ce sujet :

$ 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

Remarquez que la partie « 1:1 » est l'adresse de bus du hub. S'il s'agit d'un hub externe, cette adresse changera chaque fois que le hub est connecté à l'ordinateur.

En revanche, les numéros de port ne changent jamais (tant que le périphérique reste connecté sur le même port physique).

Maintenant que nous connaissons le numéro de port du périphérique, réinitialisons-le :

$ 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

Après la première commande, le périphérique est signalé comme déconnecté dans le journal du noyau :

usb 1-6: USB disconnect, device number 4

Après la seconde commande, l'ordinateur se comporte comme si le périphérique venait d'être branché physiquement :

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

En raison de cette réénumération, une nouvelle adresse de bus est attribuée au périphérique :

$ 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

Est-ce donc équivalent à débrancher physiquement le périphérique USB puis à le rebrancher ? La réponse est : pas forcément. Dans la plupart des cas, les commandes de hubpower ne coupent pas réellement l'alimentation du périphérique USB. Le périphérique continue donc de recevoir sa tension VBUS (5 V). Très peu de hubs USB coupent réellement l'alimentation. Plus de détails ci-dessous.

Avec l'immense majorité des hubs, la première commande (« power off ») se contente de mettre le port dans un état qui l'amène à ignorer le périphérique. La seconde commande (« power on ») rend au port son état naturel. Le résultat, c'est que le périphérique est détecté et que sa procédure d'initialisation démarre. Entre autres, le périphérique est réinitialisé et énuméré, puis son pilote l'initialise.

Une déconnexion physique est donc généralement plus efficace que ces commandes, en particulier si le périphérique tire son alimentation de la prise USB. Mais si le hub coupe réellement le VBUS, hubpower est tout aussi efficace.

Remarquez que dans cet exemple, les commandes ont été envoyées au contrôleur USB de la carte mère (le hub racine). La raison est que le périphérique USB était directement connecté à l'ordinateur. Si ce périphérique est connecté à l'ordinateur par l'intermédiaire d'un hub externe, les commandes doivent être envoyées à ce hub externe. Dans les deux cas, hubpower s'utilise de la même façon.

hubpower ne prend malheureusement pas en charge l'USB 3.0 (SuperSpeed). Plus de détails là-dessus plus bas.

Différences entre les deux méthodes

En quoi la seconde méthode (avec hubpower) diffère-t-elle de la première (avec usbreset) ? Pour résoudre un problème avec un périphérique, les deux méthodes font essentiellement la même chose : elles envoient une commande de reset. La manière dont cela se produit est radicalement différente, mais c'est toujours cette commande de reset qui, si quelque chose peut résoudre le problème, a le plus de chances de le faire.

Il y a tout de même quelques différences importantes :

Piloter un équipement électrique (?)

Même si le sujet principal de cette page est de réparer un problème avec un périphérique USB, il y a aussi un effet secondaire intéressant : il est parfois possible de piloter l'alimentation 5 V du port USB. En d'autres termes, un hub USB simple et bon marché peut servir à allumer et éteindre une alimentation capable de fournir 2,5 watts.

C'est amplement suffisant pour commander un relais électromécanique. Il ne reste donc qu'un seul composant à ajouter pour piloter un appareil qui fonctionne en 110 V / 220 V. Bon, c'est aussi une bonne idée d'ajouter une simple diode, afin de protéger le hub USB contre d'éventuels dégâts. Mais c'est tout.

Malheureusement, la possibilité de couper l'alimentation reste optionnelle : selon la section 11.11 de la spécification USB 2.0, un hub peut avoir des interrupteurs d'alimentation qui coupent la tension 5 V d'un port lorsque celui-ci est dans l'état Powered-Off. Autrement, un interrupteur d'alimentation peut être utilisé pour couper l'alimentation de plusieurs ports à la fois (c'est le « ganged power switching »). Le but de la commande d'alimentation est surtout de pouvoir éteindre les périphériques USB qui consomment trop de courant, afin que les autres ports puissent continuer à fonctionner normalement.

Mais comme déjà mentionné, la coupure physique de l'alimentation est optionnelle. Ce que hubpower fait réellement, c'est changer la valeur de l'attribut PORT_POWER du port (ce qu'on appelle une « feature » dans les spécifications USB). Cette modification touche deux aspects différents du port du hub :

PORT_POWER est expliqué plus en détail ci-dessous.

Est-ce que mon hub coupe réellement la tension ?

Comment savoir si votre hub coupe réellement la tension ? Le seul moyen d'en être sûr, c'est de tester. Branchez quelque chose qui n'est pas un périphérique USB, mais qui consomme du courant sur le port USB. De vrais périphériques USB peuvent prêter à confusion. Par exemple, une souris optique éteint généralement sa LED en réponse à la commande « power off », même si la tension 5 V reste présente.

Chaque hub déclare, dans les informations visibles avec « lsusb -v » (dans le Hub Descriptor, défini à la section 11.23.2.1 de la spécification), s'il coupe la tension et à quelle granularité. Le hub peut déclarer qu'il ne coupe pas la tension, qu'il la coupe par groupes de ports (« gangs »), ou qu'il la coupe individuellement pour chaque port. Selon la section 11.11 de la spécification USB 2.0, « un hub avec interrupteurs d'alimentation peut couper l'alimentation de tous les ports en un seul groupe, de chaque port individuellement, ou avoir un nombre quelconque de groupes d'un ou plusieurs ports ».

Cependant, cette information n'est pas fiable. J'ai rencontré plusieurs hubs qui déclarent couper la tension, mais aucun ne le faisait réellement.

Par exemple, voici un extrait de la sortie de « 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

Cela semble optimiste, non ? En réalité, ce hub ne coupe pas du tout les tensions.

À l'inverse, voici un hub de carte mère tout ce qu'il y a de plus ordinaire :

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

Ce hub admet donc qu'il ne prend en charge aucun contrôle de tension.

Remarquez qu'en plus des informations sur la commutation d'alimentation, il y a aussi l'état de chaque port sous la rubrique « Hub Port Status ». Ces bits d'état sont définis dans la table 11-21 de la spécification USB 2.0 (section 11.24.2.7.1). C'est la même information que celle récupérée par hubpower.

L'outil uhubctl

L'idée de piloter un relais avec un hub USB est à l'origine de nombreuses initiatives. Cela vaut le coup de regarder uhubctl, principalement parce que ce projet tient une liste de hubs USB qui coupent réellement la tension.

Cet outil est clairement destiné uniquement au contrôle de la tension, et non à la résolution de problèmes avec un périphérique USB. Par exemple, par défaut, uhubctl ignore les hubs qui ne déclarent pas être capables de couper individuellement la tension de chaque port.

Ces commandes sont équivalentes aux commandes hubpower ci-dessus :

$ 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

La syntaxe de la commande est beaucoup moins pratique. Elle est expliquée brièvement ci-dessous.

uhubctl est basé sur libusb, ce qui fait que cet outil fonctionne aussi sur d'autres systèmes d'exploitation. En revanche, hubpower accède directement au fichier du hub dans /dev/bus/usb/ (ou /proc/bus/usb/), ce qui ne fonctionne que sous Linux.

uhubctl prend aussi en charge l'USB 3.x (voir le commit git et son suivi). Cependant, il semble que personne ne se soit soucié de ce qui se passe quand un vrai périphérique USB est connecté au hub : mes tentatives pour réinitialiser un périphérique SuperSpeed ont donné des résultats bizarres. Lorsque l'alimentation était rétablie, le périphérique restait dans l'état Polling et n'était pas énuméré. De plus, pendant que l'alimentation était coupée, « lsusb -v » restait bloqué. Quelque chose clochait là.

Voici les commandes pour couper puis rétablir l'alimentation sur un port SuperSpeed. Plus de détails sur le SuperSpeed plus bas.

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

Explications sur PORT_POWER

Lorsqu'une commande arrive de l'hôte pour mettre PORT_POWER à zéro, le port entre sans condition dans l'état Powered-Off. C'est vrai même si le hub continue d'alimenter le périphérique en 5 V. Il y a d'autres raisons pour lesquelles PORT_POWER peut passer à zéro, en particulier une surintensité sur le port (le périphérique consomme trop de courant).

La seule façon de remettre PORT_POWER à '1' est de passer par une commande de l'hôte. Cela met le port dans l'état Disconnected. Les ports USB sur lesquels rien n'est branché se trouvent normalement dans cet état. Lorsqu'un périphérique est détecté sur le port, l'état passe à Disabled après un court délai. Si le périphérique est déjà connecté et que PORT_POWER passe à '1', cela se produit immédiatement.

À partir de cet état, le seul chemin vers l'activation du périphérique consiste à réinitialiser le port (avec PORT_RESET, voir plus bas). Seul l'hôte peut le faire. Par conséquent, tout ce que le hub peut faire, c'est signaler à l'hôte qu'un périphérique est connecté et demande de l'attention.

C'est là qu'intervient l'un des bits d'état du port : PORT_CONNECTION. Ce bit doit être à zéro lorsque le port est dans l'état Powered-Off ou Disconnected. PORT_CONNECTION passe à '1' lorsque le port transite de l'état Disconnected à l'état Disabled. Un changement de PORT_CONNECTION génère un événement de hub, de sorte que le pilote est notifié (c'est-à-dire qu'un appel à port_event() dans le hub.c du noyau est effectué avec USB_PORT_FEAT_C_CONNECTION activé). Le pilote répond en réinitialisant le port (en mettant le bit PORT_RESET du port à '1') et en énumérant le périphérique connecté.

Tout cela concerne l'USB 2.0. La figure 11-10 de la spécification USB 2.0 montre comment un port de hub change d'état.

Contrôler directement PORT_RESET et PORT_ENABLE

hubpower permet aussi de modifier directement deux autres attributs : PORT_RESET et PORT_ENABLE. En fait, j'ai ajouté cette possibilité à mon propre fork de cet outil. Notez toutefois que tout ce que l'on peut faire ainsi, c'est faire échouer volontairement le périphérique USB (et donc amener l'ordinateur à réinitialiser le périphérique pour corriger le problème). Maintenant, plus de détails :

PORT_RESET doit être mis à '1' par l'hôte pour déclencher un reset de port, conformément à la section 11.5.1.5 de la spécification USB 2.0. Le hub remet PORT_RESET à '0' après avoir terminé le reset. Le hub ne lance jamais un reset de port de sa propre initiative, et l'hôte n'a pas le droit d'écrire '0' dans cet attribut (section 11.24.2.7.1.5).

Le hub ignore PORT_RESET si le port est dans l'état Powered-Off ou Disconnected.

En ce qui concerne PORT_ENABLE : lorsque cet attribut passe à '0', le port passe dans l'état Disabled. Cela peut se produire à la suite d'une demande de l'hôte, mais aussi de la déconnexion du périphérique USB, du fait que le port est hors tension, ou d'une erreur pendant le processus de reset.

PORT_ENABLE ne peut repasser à '1' qu'à la suite d'une demande de reset de port envoyée par l'hôte (section 11.24.2.7.1.2 de la spécification USB 2.0).

L'hôte n'a donc pas le droit de mettre PORT_RESET à '0' ni de mettre PORT_ENABLE à '1'. Toute tentative en ce sens aboutira à une réponse d'erreur de la part du hub (la commande ioctl() correspondante renverra un code d'erreur).

hubpower peut demander directement un reset en mettant le PORT_RESET du port à '1'. Cela réinitialisera le périphérique USB, et le périphérique oubliera son adresse de bus par la même occasion. Le périphérique ne sera donc plus accessible.

Cette commande est envoyée directement au hub ; le pilote du hub dans le noyau Linux ne saura donc pas que cela s'est produit. En fait, l'ordinateur ne remarquera pas que quelque chose a changé tant qu'il n'essaiera pas d'accéder au périphérique USB. La suite dépendra du pilote du périphérique. Le périphérique sera traité comme s'il avait une erreur matérielle. Des mesures de correction seront donc prises. Le plus probable est que cela inclura un reset.

Utiliser hubpower pour provoquer directement un reset donnera donc probablement le résultat souhaité, mais avec beaucoup d'agitation inutile. PORT_POWER fait cela plus élégamment. Le seul avantage possible d'un PORT_RESET direct est que le pilote pourrait se dire : « hé, ce périphérique a vraiment un problème, faisons quelque chose de radical pour le réparer ». Et cela pourrait aider.

Quant à la manipulation de PORT_ENABLE, la même chose se produira : le périphérique disparaîtra soudainement. Même dans ce cas, l'ordinateur ne saura pas immédiatement que quelque chose s'est passé : selon la section 11.24.2.7.2.2 de la spécification USB 2.0, une notification de changement sur PORT_ENABLE (c'est-à-dire C_PORT_ENABLE) n'est déclenchée que si le port devient désactivé en raison d'une erreur sur la liaison. La spécification dit explicitement que cette notification n'est pas émise pour toute autre raison.

Donc, mettre PORT_ENABLE à zéro aura à peu près le même effet que modifier directement PORT_RESET. Avec un inconvénient : selon la section 10.14.2.6.1 de la spécification USB 3.0, PORT_ENABLE « n'est pas pris en charge par les hubs SuperSpeed ».

SuperSpeed (USB 3.x)

Tout d'abord : si vous lisez cette page parce que vous avez un problème avec un périphérique USB 3.x, demandez-vous si vous avez vraiment besoin du débit offert par l'USB 3.x. Si la réponse est non, essayez de connecter le périphérique à l'ordinateur via un hub USB qui ne prend pas en charge l'USB 3.x (ou avec un court câble USB 2.0). Cela pourrait suffire à résoudre le problème.

Le SuperSpeed USB (ce qui signifie la même chose que l'USB 3.x) coexiste en parallèle avec l'USB 2.0. Chaque périphérique SuperSpeed est en réalité constitué de deux périphériques : un périphérique distinct pour le SuperSpeed et un autre périphérique distinct pour l'USB 2.0. Chacune de ces deux versions USB utilise des fils séparés du câble USB. Elles sont mutuellement indépendantes, électroniquement et conceptuellement.

Les spécifications USB exigent que chaque périphérique SuperSpeed soit composé de ces deux périphériques, même si, concrètement, cela n'est pas indispensable. En d'autres termes, un périphérique SuperSpeed qui ne prend pas en charge l'USB 2.0 fonctionne correctement lorsqu'il est connecté à un port SuperSpeed.

Lorsqu'un périphérique SuperSpeed est connecté à un port SuperSpeed, la première tentative se fait par l'interface SuperSpeed. Si elle échoue, une tentative de connexion en USB 2.0 est faite. En pratique (et conformément à la spécification), un périphérique USB ne se connecte jamais par les deux versions en même temps. C'est toutefois possible, et le périphérique USB se comporterait alors comme deux périphériques distincts.

Un hub SuperSpeed est constitué de deux hubs en parallèle : un pour l'USB 2.0 et un pour le SuperSpeed. Lorsqu'on connecte un hub USB SuperSpeed externe à un ordinateur, deux hubs sont ajoutés au système. Ils apparaissent comme deux périphériques distincts. Un périphérique USB ordinaire n'a pas le droit d'utiliser les deux versions en parallèle, mais un hub, lui, doit le faire.

Si un hub SuperSpeed est connecté à un port USB 2.0, il se comporte comme un hub USB 2.0 ordinaire.

D'une manière générale, chacun de ces deux hubs fonctionne indépendamment de l'autre. Chaque hub a ses propres ports, et chacun de ces ports fonctionne indépendamment. En particulier, si l'on modifie les paramètres d'un port sur l'un de ces hubs en parallèle, cela n'a aucun effet sur les ports de l'autre hub.

Autre conclusion : si « lsusb -t » montre qu'un périphérique est connecté à l'ordinateur par l'intermédiaire du hub racine SuperSpeed, c'est qu'il fonctionne comme un périphérique SuperSpeed. Autrement dit, son débit est de 5 Gbit/s ou plus. De même, si le périphérique est connecté par le hub racine USB 2.0, son débit est de 480 Mbit/s ou moins.

SuperSpeed et PORT_POWER

Rappelons que ce que fait réellement hubpower, c'est modifier l'attribut PORT_POWER du port.

Mais un hub SuperSpeed est constitué de deux hubs en parallèle. Chacun de ces deux hubs possède son propre attribut PORT_POWER, indépendant, pour chaque port. Alors, quand le hub doit-il couper la tension VBUS ? Chaque interrupteur d'alimentation physique dépend de deux attributs PORT_POWER, un pour chacun des hubs en parallèle.

La table 10-2 de la spécification USB 3.0 donne la table de vérité indiquant si le hub doit activer ou non l'alimentation. On peut la résumer ainsi : si le hub se comporte uniquement comme un hub USB 2.0 (par exemple s'il est connecté à un ordinateur qui ne prend pas en charge le SuperSpeed), il suit le PORT_POWER de sa partie USB 2.0. S'il est connecté en tant que hub SuperSpeed (ou si les deux hubs en parallèle sont connectés), VBUS n'est coupée que si les deux PORT_POWER sont à zéro.

Si cela paraissait compliqué, c'était en réalité la partie facile : la partie SuperSpeed du hub possède une machine à états (state machine) interne différente. C'est assez logique, car l'établissement de la liaison (link training) se fait différemment. Mais cette machine à états a trois états différents (au lieu d'un seul, comme l'USB 2.0) qui peuvent s'appliquer lorsque PORT_POWER est à zéro :

Le but de ces deux derniers états est de garantir que si un périphérique SuperSpeed disposant de sa propre alimentation est connecté au port, la connexion ne retombe pas en USB 2.0. Cela se produirait parce que le périphérique n'aurait aucun moyen de savoir qu'il est connecté à un port SuperSpeed.

Qu'avons-nous appris de tout cela ? Principalement que réinitialiser un périphérique en modifiant PORT_POWER sur un hub SuperSpeed n'est pas aussi simple qu'avec un hub USB 2.0.

SuperSpeed : warm reset et hot reset

L'USB 2.0 dispose d'une seule méthode simple pour réinitialiser le périphérique : les deux fils sont mis à la terre par le hub (SE0) pendant 10 ms. Les périphériques SuperSpeed, en revanche, ont deux façons de réinitialiser un périphérique : le warm reset et le hot reset. Il ne faut pas les confondre avec le PowerOn Reset et l'Inband Reset, qui sont des termes utilisés pour définir la raison du reset. Inband Reset signifie que le reset se produit à la suite d'une demande de l'hôte. Cela peut aboutir à un warm reset ou à un hot reset, selon le type de la demande (plus de détails juste en dessous).

Il est important de distinguer le warm reset du hot reset : en particulier, un warm reset implique l'arrêt du flux de données et sa remise en route depuis le début. Un hot reset est envoyé sur le flux de données lui-même et maintient ce flux actif. Le hot reset est donc beaucoup plus rapide, mais si le problème du flux de données peut être corrigé en le redémarrant, c'est un warm reset qu'il faut. Un exemple d'un tel problème est la présence d'erreurs binaires sur la couche physique. Couper le flux binaire (bitstream) et repartir de zéro peut corriger le problème, peut-être parce que cela permet de corriger un réglage sous-optimal de l'égaliseur (equalizer).

Rappelons que usbreset effectue un ioctl() avec USBDEVFS_RESET. Entre toutes les autres choses qui se passent, cela met PORT_RESET à zéro, quelle que soit la version USB du périphérique (en tout cas à partir du noyau Linux v5.16).

Selon la section 7.4.2 de la spécification USB 3.0, une requête PORT_RESET donne lieu à un Hot Reset. Cela signifie que si le flux de données est actif, il n'est pas interrompu, mais que la commande de reset est envoyée sur ce flux. Cette spécification définit aussi BH_PORT_RESET (feature numéro 28), qui force un warm reset (sauf si le port est désactivé). Ce reset est plus fondamental : il interrompt le flux de données et relance la procédure permettant de le rétablir (au moyen de la signalisation LFPS). La différence importante est que si le flux de données doit être relancé, BH_PORT_RESET le fera, mais PORT_RESET ne le fera pas.

La connexion d'un nouveau périphérique SuperSpeed implique un Warm Reset. Il est donc dommage qu'il n'existe apparemment aucun outil capable de faire convenablement le coup de PORT_POWER sur un port SuperSpeed. Comme mentionné plus haut, uhubctl peut techniquement le faire, mais le périphérique se retrouve dans un état douteux.


Notes en vrac

Voici des bribes d'informations qui peuvent être utiles, mais qui n'ont pas vraiment de contexte attitré.

Cette page a été traduite de l’anglais par une machine. En cas de doute, veuillez vous reporter au texte original
Copyright © 2021-2026. All rights reserved. (dcc38493)