24 points par GN⁺ 21 일 전 | Aucun commentaire pour le moment. | Partager sur WhatsApp
  • 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 un PID (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)
    • 18d1 est le VID de Google, et 4ee0 le PID du bootloader Nexus/Pixel
  • La commande lsusb -t permet de vérifier la classe et l’état du pilote
    • Class=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 combinaison VID:PID donné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.sys est 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
  • 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, bDeviceClass et d’autres informations sur le périphérique
  • La commande lsusb -v permet d’inspecter en détail tous les descripteurs (périphérique, configuration, interface, endpoint, etc.)
    • Exemple : l’interface Android Fastboot possède des endpoints Bulk IN(0x81) et Bulk OUT(0x02)

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
  • 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ées
    • OUT : 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 vaut 0, 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"
  • 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
      
    • OKAY indique un état de succès, 0.4 est la version de Fastboot

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.

Aucun commentaire pour le moment.