USB pour les développeurs logiciels : introduction à l’écriture de pilotes USB en espace utilisateur
(werwolv.net)- Le développement de pilotes USB est souvent perçu comme un travail au niveau noyau, mais il peut en réalité être réalisé en espace utilisateur avec une complexité comparable à la programmation socket
- Avec libusb, il est possible d’effectuer l’énumération des périphériques, les transferts de contrôle, ainsi que l’envoi et la réception de données sans écrire de code noyau
- La communication USB repose sur quatre types de transfert — Control, Bulk, Interrupt, Isochronous — ainsi que sur les directions IN/OUT, chaque endpoint fonctionnant comme un canal unidirectionnel
- En prenant comme exemple le protocole Fastboot des appareils Android, l’article montre en code comment échanger des commandes et des réponses via des endpoints Bulk
- Il est possible d’implémenter un pilote USB complet en espace utilisateur, et tous les protocoles USB partagent la même structure de base
Introduction
- Les pilotes pour périphériques USB paraissent difficiles parce qu’on pense qu’ils nécessitent de manipuler du code noyau, alors qu’en pratique leur complexité est comparable à celle d’une application utilisant des sockets
- Même un développeur ayant peu d’expérience matérielle peut apprendre à manipuler l’USB en espace utilisateur
- Il existe des ressources qui détaillent le fonctionnement interne de l’USB, mais elles restent difficiles d’accès pour les débutants
- L’usage de l’USB ne demande pas de connaissances de niveau systèmes embarqués et peut être abordé comme les sockets réseau
Périphérique USB
- L’exemple utilisé est un smartphone Android en mode bootloader
- Il est facile à trouver, le protocole est simple, et l’absence de pilote par défaut dans l’OS en fait un bon support d’expérimentation
- L’entrée en mode bootloader varie selon les appareils, mais se fait généralement via une combinaison du bouton d’alimentation et des boutons de volume
Énumération manuelle du périphérique
- L’énumération (Enumeration) est le processus par lequel l’hôte interroge le périphérique pour l’identifier, et elle est exécutée automatiquement lors de la connexion
- Les périphériques standard chargent automatiquement un pilote en fonction de leur classe USB, tandis que les périphériques spécifiques au constructeur utilisent un
VID(Vendor ID) et unPID(Product ID) - Sous Linux, on peut consulter les informations du périphérique avec la commande
lsusb- Exemple :
ID 18d1:4ee0 Google Inc. Nexus/Pixel Device (fastboot) 18d1est le VID de Google, et4ee0le PID du bootloader Nexus/Pixel
- Exemple :
- La commande
lsusb -tpermet de vérifier la classe et l’état du piloteClass=Vendor Specific Class,Driver=[none]indiquent que l’OS n’a chargé aucun pilote
- Sous Windows, on peut obtenir les mêmes informations via le Gestionnaire de périphériques ou USB Device Tree Viewer
Énumération du périphérique avec libusb
- La bibliothèque libusb permet de communiquer avec des périphériques USB en espace utilisateur sans écrire de code noyau
libusb_hotplug_register_callback()permet de configurer l’exécution d’un callback lorsqu’un périphérique correspondant à une combinaisonVID:PIDdonnée est connecté- Après le lancement du programme, le message
"Device plugged in!"s’affiche lorsque le périphérique est branché - Sous Linux, cela fonctionne par défaut et, si nécessaire,
libusb_detach_kernel_driver()permet de détacher le pilote noyau - Sous Windows, le pilote
Winusb.sysest nécessaire ; s’il n’est pas présent, l’outil Zadig peut être utilisé pour le remplacer manuellement
Communication avec le périphérique
- La première communication avec un périphérique USB s’effectue via l’endpoint Control (adresse 0x00)
libusb_control_transfer()permet d’envoyer une requête standard (GET_STATUS) pour lire l’état du périphérique- Exemple de réponse :
01 00→ le premier octet indique Self-Powered, le second l’absence de prise en charge de Remote Wakeup
- Exemple de réponse :
- On peut ensuite récupérer le descripteur du périphérique avec une requête GET_DESCRIPTOR
- Les données retournées contiennent notamment
idVendor,idProduct,bDeviceClasset d’autres informations sur le périphérique
- Les données retournées contiennent notamment
- La commande
lsusb -vpermet d’inspecter en détail tous les descripteurs (périphérique, configuration, interface, endpoint, etc.)- Exemple : l’interface
Android Fastbootpossède des endpoints Bulk IN(0x81) et Bulk OUT(0x02)
- Exemple : l’interface
Endpoints
- Les endpoints sont un concept proche des ports réseau : ce sont les canaux par lesquels le périphérique envoie et reçoit des données
- Le type et la direction de chaque endpoint sont définis dans les descripteurs
-
Type de transfert Control
- Tous les périphériques en possèdent un, toujours à l’adresse
0x00 - Il sert à la configuration initiale et aux demandes d’informations sur le périphérique
- Il n’appartient pas à une interface et fait partie du périphérique lui-même
- Tous les périphériques en possèdent un, toujours à l’adresse
-
Type de transfert Bulk
- Utilisé pour les transferts volumineux de données non temps réel
- Exemples : Mass Storage, CDC-ACM (série), RNDIS (Ethernet)
- La bande passante est élevée mais la priorité est faible
-
Type de transfert Interrupt
- Utilisé pour les petits transferts à faible latence
- Utilisé notamment par les claviers et les souris pour sonder rapidement les entrées utilisateur
- Il ne s’agit pas d’une véritable interruption matérielle : c’est l’hôte qui effectue les requêtes périodiquement
-
Type de transfert Isochronous
- Utilisé pour les données volumineuses sensibles au temps (audio, streaming vidéo)
- En cas de latence, la dégradation de qualité devient immédiatement visible
- Dans libusb, il est traité de manière asynchrone
-
Directions IN / OUT
- L’USB repose sur une architecture centrée sur l’hôte, et le périphérique ne peut pas transmettre de données tant qu’il n’a pas reçu de requête
IN: direction dans laquelle l’hôte reçoit les donnéesOUT: direction dans laquelle l’hôte envoie les données- Si le bit de poids fort (MSB) de l’adresse de l’endpoint vaut
1, il s’agit de IN ; s’il vaut0, il s’agit de OUT - Jusqu’à 127 endpoints personnalisés peuvent être utilisés (
0x00étant réservé au Control) - Les endpoints sont unidirectionnels et sont souvent organisés en paires IN/OUT, comme dans l’interface Fastboot
Protocole Fastboot
- Fastboot est un protocole de communication avec le bootloader Android : on envoie une chaîne de commande et on reçoit un code d’état de 4 octets ainsi que des données
- Exemples :
Host: "getvar:version"→Client: "OKAY0.4"Host: "getvar:nonexistant"→Client: "OKAY"
- Exemples :
- Exemple de code utilisant libusb pour envoyer une commande Fastboot
- L’interface 0 est réservée via
libusb_claim_interface() - La commande
"getvar:version"est envoyée sur l’endpoint Bulk OUT(0x02) - La réponse est reçue via l’endpoint Bulk IN(0x81)
- Exemple de sortie :
Request: getvar:version Response: OKAY0.4 OKAYindique un état de succès,0.4est la version de Fastboot
- L’interface 0 est réservée via
Conclusion
- Il est possible d’implémenter un pilote USB complet en espace utilisateur sans écrire de code noyau
- Tous les pilotes USB reposent sur les mêmes principes fondamentaux ; seul le protocole change
- Même les protocoles plus complexes (comme MTP) conservent la même structure de base et peuvent être abordés avec des concepts similaires à la communication par sockets
Aucun commentaire pour le moment.