Développez vos pilotes de périphériques USB
Signature : | Mis en ligne le : 20/09/2008
Catégorie(s) :
  • GNU/Linux Magazine HS
  • | Domaine :
    1 commentaire(s)

    Retrouvez cet article dans : Linux Magazine Hors série 17

    Les périphériques USB se sont largement répandus : scanner, appareil photo, disques durs externes… Cette connectique offre en effet de nombreux avantages (configuration "à chaud", débit de 480 Mbits/s pour la version USB2.0) et semble à présent incontournable. Cet article est une introduction à  la gestion de l’USB sous Linux et à l’écriture de pilotes USB. Il s’inscrit dans la lignée des articles déjà parus dans ce même journal en mai 2000 (introduction à l’écriture driver) et septembre 2002 (écriture de pilotes PCI). Après quelques précisions sur les spécifications USB (version 1.1), nous décrirons l’interface USB présentée par le noyau et détaillerons  l’écriture d’un mini-driver nous permettant de lire directement les donnés d’une souris USB. Connaissances et matériel requis :
    • Langage C
    • Une souris USB
    • Noyau 2.4.x

    Aperçu de l’architecture USB et concepts clefs

    Un ordinateur ne comporte qu’un seul contrôleur USB implanté sous forme matérielle et logicielle. Cet unique contrôleur intègre un hub racine fournissant un ou plusieurs points d’attachements. Les périphériques USB sont de deux sortes :
    • Les hubs offrant de nouveaux points d’attachements ;
    • Les "fonctions" offrant de nouvelles fonctionnalités : scanner, souris...
    Tous présentent la même interface USB au travers de :
    • Leur compréhension du protocole USB ;
    • Leur réponse aux opérations USB standards : configuration, reset... ;
    • Leur manière de se décrire.

    Topologie du bus

    Topologie physique : l’USB connecte des périphériques USB à un hôte USB. L’interconnexion physique décrit une topologie en étoile sur plusieurs niveaux.

    /img-articles/lmhs/17/art-6/fig-1.jpg

    Un hub est au centre de chaque étoile. Topologie logique : L’hôte communique avec chaque périphérique comme s’il était directement connecté  au hub racine.

    Support physique

    L’USB transfère signal et alimentation dans un câble à quatre fils. Il autorise deux taux de transfert : full-speed (12 Mb/s) et low-speed (1,5 Mb/s) pour les périphériques ne nécessitant pas une large bande passante, comme les souris par exemple.

    Flux de communication

    Les communications USB se déroulent au travers de deux interfaces logicielles :
    • Host Controller Driver (HCD), couche logicielle permettant de faire abstraction du contrôleur USB matériel. Elle fournit une SPI (System Programming Interface) permettant d’interagir avec le contrôleur USB, et de cacher les spécificités de l’implantation matérielle.
    • USB Driver (USBD), le pilote USB qui fournit les fonctionnalités propres au périphérique.

    Périphérique logique

    Un périphérique USB apparaît au système comme une collection de "endpoints". Ce sont d’uniques parties adressables du périphérique et sont sources ou cibles du flux de communication entre le périphérique et l’hôte. Chaque "endpoint" est caractérisé par un identifiant unique (fixé par le constructeur) appelé endpoint number et constitue une connexion supportant un flux de données du périphérique vers l’hôte ou inversement. Chaque endpoint possède une direction de flux prédéterminée (IN/OUT) et différents paramètres décrivant les caractéristiques des transferts qu’il est susceptible de supporter :
    • Temps de latence et fréquence d’accès au bus ;
    • Bande passante ;
    • Endpoint address ;
    • Gestion d’erreur ;
    • Taille maximale des paquets qu’il peut recevoir et envoyer ;
    • Type de transfert ;
    • Direction des transferts.
    Les "pipes" sont l’abstraction logique représentant l’association entre un "endpoint" et l’ USBD. Les endpoints sont regroupés en "endpoints sets" pour former des interfaces. Une interface peut être assimilée à une vue du périphérique. Chaque périphérique USB implémente de manière obligatoire une méthode de contrôle par défaut utilisant un endpoint particulier possédant le numéro 0. A cet endpoint est associé un tube de contrôle par défaut (Default Control Pipe), qui fournit un accès aux informations de configuration du périphérique. Ce tube supporte des transferts de contrôle permettant de configurer le périphérique. Les endpoints de numéro 0 sont accessibles dès que le périphérique est attaché au bus. Sous Linux, ces informations sont lisibles soit directement via /proc/bus/usb/devices, soit par des outils tels que lsusb (mode texte) ou usbview (mode graphique).

    Types de transfert

    L’USB définit quatre types de transfert, optimisés en termes de temps de latence, contraintes de taille… suivant le type de service demandé.
    • Transfert de contrôle : ponctuel, non périodique, à l’initiative de l’hôte, utilisé typiquement pour les opérations de configuration et de contrôle de statut ;
    • Transfert synchrone : périodique, continu et pouvant nécessiter une certaine bande passante (utilisé pour une camera USB par exemple) ;
    • Transfert d’interruption : données de petite taille, faible temps de latence, faible fréquence (utilisé pour une souris USB par exemple) ;
    • Bulk transfert : large quantité de données, non périodique, ne nécessitant pas une bande passante prédéterminée ( utilisés pour un scanner USB par exemple ).

    La SPI

    Un pilote USB utilise uniquement l’interface de programmation fournie par le HCD pour gérer un périphérique, contrairement à un pilote PCI, PCMCIA…, qui utilise des adresses mémoires ou des ports d’E/S. Sous Linux, l’interface est fournie par les modules usb_uhci (ou usb_ohci suivant le type de contrôleur) et usbcore. Les structures et fonctions que nous allons utiliser sont décrites dans le fichiers d’en-tête linux/usb.h. Au niveau du code en C, nous  utiliserons les structures, fonctions et macros suivantes :
    Une telle structure est allouée par le HCD à chaque branchement d’un nouveau périphérique USB sur le bus. Elle contient toutes les informations concernant la structure logique du périphérique : identifiant vendeur, identifiant constructeur, endpoints… Toutes les informations fournit par usbview sont accessibles via cette structure, qui est transmise au pilote afin de lui permettre de reconnaître dans un premier temps le périphérique qu’il va prendre en charge puis de communiquer avec.
    Cette fonction est appelée  à chaque nouvel attachement d’un périphérique au bus USB. L’appel de cette fonction est réalisé avec la structure usb_device décrite précédemment, correspondant au nouveau périphérique. Le rôle de cette fonction est de vérifier les informations de configuration du périphérique et de s’attacher le périphérique le cas échéant. L’attachement du pilote au périphérique est réalisé si la fonction retourne un pointeur non nul ( généralement un pointeur sur une structure de données spécifique au pilote et allouée au sein de celui-ci).
    Fonction appelée lors du débranchement du périphérique. Son rôle est la libération des ressources mobilisées par le driver. Ptr est un pointeur sur la structure de données retournée par probe.
    Structure représentant le driver USB. Contient le nom du driver, un pointeur sur les fonctions probe, disconnect et sur une structure file_operations classique (stockant les différentes fonctions implémentées pour le périphérique : lecture, écriture…)
    Permet d’enregistrer (et de dé-enregistrer) le pilote auprès du HDC.
    Pour échanger des informations avec le périphérique, nous avons besoin de dire au sous-système USB comment communiquer. Cette tâche est accomplie en remplissant une structure urb et en la passant à la fonction usb_submit_urb.
    Pour allouer une structure URB.
    Pour créer un pipe supportant le transfert de type interruption entre le périphérique logique udev et le endpoint d’adresse endpointadresse.
    Cette macro permet de remplir une structure urb qui spécifiera au HCD que le type de transfert sera "interruption". Le périphérique concerné est pointé par udev, l’urb utilisé pour les transfert de données est pointé par purb. pipe représente le pipe que l’on aura créé par la macro précédente. Fonction_de rappel est un pointeur vers une fonction de prototype void fonction_de_rappel (struct urb *purb). Cette fonction sera appelée après chaque interruption spécifiant l’achèvement d’un transfert de données du périphérique vers l’hôte. (Elle doit notamment respecter les contraintes spécifiques au code appelé dans un contexte d’interruption). Interval représente l’intervalle en ms entre les interruptions. tampon est pointeur sur une zone de mémoire non "swappable" qui contient les données transférées depuis le périphérique. (Une version de cette macro existe pour chaque type de transfert : FILL_BULK_URB, FILL_ISO_URB…).
    Incrémente et décrémente le compteur d’utilisation des modules du HCD. Les autres fonctions et structures utilisées ne sont pas spécifiques à l’USB :
    Incrémentation et décrémentation du compteur d’usage du module. (colonne used de lsmod). Empêche le déchargement d’un driver lorsque celui-ci est en cours d’utilisation.
    Structure et fonctions permettant d’endormir et de réveiller un processus sur une file d’attente.
    Fixe les fonctions d’initialisation et de destruction du driver.

    Notre premier driver

    Ces précisions étant faites, nous allons pouvoir passer à l’écriture proprement dite de notre pilote de souris USB. Au niveau des fonctionnalités, notre pilote implémentera seulement une lecture en mode bloquant. La première tâche à effectuer est la découverte des informations de configuration de la souris USB dont on veut écrire le pilote. Je branche ma souris sur un port USB. usbview me donne alors les informations :

    /img-articles/lmhs/17/art-6/fig-2.jpg

    usbview nous indique l’identifiant vendeur et l’identifiant constructeur, ce qui permettra à la fonction probe de reconnaître le périphérique que nous voulons prendre en charge. On apprend aussi que l’on communiquera avec le endpoint d’adresse 0x81, sur l’interface zéro avec un type de transfert interruption, une taille maximale de paquet de quatre octets, etc. Pour stocker toutes les données nécessaires au pilote, on utilisera notre propre structure : Struct LMAG_priv. Cette structure sera dynamiquement allouée à la fonction d’initialisation du pilote. L’implémentation de la lecture bloquante est réalisée comme suit :
    • Un processus qui veut lire des données provenant du périphérique est mis en sommeil sur une file d’attente.
    • Une interruption est déclenchée lorsque les données provenant du périphérique sont accessibles. Cette interruption est prise en charge par un gestionnaire d’interruption dont le rôle est d’ordonnancer une tâche permettant de réveiller, en dehors de ce contexte d’interruption, les processus endormis sur la file d’attente. Ceci est réalisé par l’intermédiaire d’une structure tasklet.
    • Lorsqu’un processus lecteur est réveillé, cela signifie que des données provenant de la souris sont accessibles. Il ne reste alors qu’à les rendre accessibles dans le contexte de l’utilisateur.
    L’accès au périphérique sera effectué grâce à un fichier spécial de numéro majeur 183 (pour tous les périphériques USB) créé par : par exemple (si le numéro mineur 251 n’est pas utilisé par un autre périphérique USB).

    CODE SOURCE

    Le makefile

    Fonctionnement

    Après compilation (utiliser le Makefile), nous obtenons le fichier LMAG_driver.o, code objet du pilote de notre souris. On crée le fichier spécial LMAG_mouse (ou tout autre nom) par la commande Pour éviter qu’un driver pré-existant soit directement chargé par le système lors du branchement de la souris, il faut "killer" le démon usbd s’il est présent sur la machine (Killall usbd). On charge le driver par insmod LMAG_driver.o et on le décharge par rmmod LMAG_driver. On peut indifféremment  brancher la souris avant de charger le driver ou charger le driver puis brancher la souris. Si la souris USB est branchée, on peut lire les données qu’elle envoie en ouvrant le fichier spécial LMAG_mouse. Par exemple : affiche par groupe de quatre octets en hexadécimal les données de la souris. La commande dmesg permet de voir les différents messages de débogage envoyés par le pilote et de tracer le passage dans les différentes fonctions. REMARQUES IMPORTANTES :
    • Afin de le simplifier, le driver a été écrit spécifiquement pour la souris que j’ai utilisée (une Microsoft IntelliMouse Explorer) puisque ses données de configurations (idvendor, endpointaddress…) ont été codées en dur dans le driver. Un véritable driver devrait lire les informations de configuration (comme usbview) et s’y adapter. Si vous en avez compris le fonctionnement, vous pouvez toutefois l’adapter sans peine à votre souris USB.
    • Un véritable pilote de souris devrait travailler les données provenant de la souris  pour les présenter selon un protocole prédéfini ( man mouse pour plus de détails).
    Bibliographie et liens
    • www.Linux-usb.org
    • Le noyau Linux, Bovet & Cesati (Editions O’Reilly)
    • Linux device drivers, Alessandro Rubini (Editions O’Reilly)
    • URB.txt dans la documentation du noyau.

    Retrouvez cet article dans : Linux Magazine Hors série 17

    Donnez votre avis - 1 commentaire(s)
  • eloy dit :
    Bonjour, Votre driver semble très interressant. Je souhaite développer un driver pour un composant usb (Humidity and Temperature Sensor). Votre tutorial me semble être une bonne base de travail. Dans un premier temps, j'ai essayé de compiler votre driver. Lors du make, j'ai une erreur : Makefile:3: /usr/src/linux/.config: No such file or directory make: *** No rule to make target /usr/src/linux/.config'. Stop. effectivement je n'ai pas de dossier /linux. Je ai donc remplacé la variable KERNELDIR du makefile par KERNELDIR=/usr/src/linux-headers-2.6.24-19-generic/ J'ai alors l'erreur suivante: make: *** No rule to make target LMAG_driver.c', needed by `LMAG_driver.o'. Stop. J'avoue être un peu perdu. Je comprend pourtant votre driver mais la compilation commence à me poser de serieux problèmes ! Pour infos, je suis sous ubuntu avec libusb et libusb-dev installé. Merci d'avance pour votre aide, Eloi
  • Vous souhaitez commenter cet article ?
    Brèves Flux RSS
    Édito : GNU/Linux Magazine 149
    Édito : GNU/Linux Magazine HS N°60
    Édito : Misc 61
    Édito : Linux Pratique 71
    Édito : Linux Essentiel N°25
    Communication RSS Com. RSS Presse
    Lancement de la plateforme de vente en ligne de PDF des Éditions Diamond ! Un...
    Misc N°61 – Communiqué de presse
    GNU/Linux Magazine N°149 – Communiqué de presse
    GNU/Linux Magazine HS N°60 – Communiqué de presse
    Linux Pratique N°71 – Communiqué de presse
    prochainement moteur de recherches des articles
     
    :
    :
    Jours heures minutes secondes
    En kiosque Flux RSS

    Le tout nouveau GNU/Linux Magazine est disponible dès maintenant chez votre marchand de journaux et sur notre site marchand.

    Découvrez le sommaire de ce numéro et un aperçu de ce magazine...

    Lire la suite...

    Le tout nouveau Misc est disponible dès maintenant chez votre marchand de journaux et sur notre site marchand.

    Découvrez le sommaire de ce numéro et un aperçu de ce magazine...

    Lire la suite...

    Le tout nouveau Linux Pratique est disponible dès maintenant chez votre marchand de journaux et sur notre site marchand.

    Découvrez le sommaire de ce numéro et un aperçu de ce magazine...

    Lire la suite...

    Le tout nouveau GNU/Linux Magazine HS est disponible dès maintenant chez votre marchand de journaux et sur notre site marchand.

    Découvrez le sommaire de ce numéro et un aperçu de ce magazine...

    Lire la suite...

    Le tout nouveau Linux Essentiel est disponible dès maintenant chez votre marchand de journaux et sur notre site marchand.

    Découvrez le sommaire de ce numéro et un aperçu de ce magazine...

    Lire la suite...

    Le tout nouveau Misc HS est disponible dès maintenant chez votre marchand de journaux et sur notre site marchand.

    Découvrez le sommaire de ce numéro et un aperçu de ce magazine...

    Lire la suite...