Linux Embedded

Le blog des technologies libres et embarquées

Gérer un pilote USB avec UDEV

Les périphériques USB sont de plus en plus fréquemment utilisés dans les systèmes industriels car la plupart des périphériques récents privilégient cette interface par rapport à d’autres bus comme PCI, FireWire (en cours d’extinction) et bien entendu l’antique lien RS-232. Plusieurs raisons sont à l’origine de cette tendance, citons le coût de production (faible pour l’USB) la facilité d’utilisation (insertion/suppression à chaud) et bien entendu la compatibilité USB de la quasi-totalité des systèmes existants des plus réduits (téléphonie) aux plus imposants (serveurs) en passant par les nombreuses cartes mères utilisées pour les systèmes embarqués.

L’API de programmation USB dans Linux est d’un abord relativement aisé, et nous reviendrons sur le sujet dans de futures publications. Il existe deux manières de piloter un périphérique USB :

  1. Ecrire un pilote de périphérique à charger dans l’espace noyau (.ko)
  2. Ecrire un programme en espace utilisateur utilisation libusb

La deuxième méthode est issue de la première puisque libusb est un « wrapper » des fonctions disponibles dans l’espace noyau. Pour des raisons de simplicité de programmation et de mise au point, elle est largement utilisée sauf si l’utilisation du périphérique nécessite la présence d’une véritable pilote (périphérique de pointage, adaptateur réseau, etc.). Dans ce cas, on doit développer un pilote et le module résultant (.ko) devra être correctement chargé dans l’espace du noyau.

De part les possibilités du bus USB (insertion et suppression à chaud) la gestion d’un pilote n’est pas aisée sachant que beaucoup de périphériques sont déjà – plus ou moins bien – pris en compte par les pilotes USB du noyau grâce à la notion de classe de périphérique HID (Human Interface Device). Le fichier virtuel /proc/bus/usb/devices permet de visualiser les paramètres de tous les périphériques USB connectés.

T:  Bus=04 Lev=01 Prnt=01 Port=01 Cnt=02 Dev#=  3 Spd=1.5 MxCh= 0
D:  Ver= 2.00 Cls=00(>ifc ) Sub=00 Prot=00 MxPS= 8 #Cfgs=  1
P:  Vendor=0461 ProdID=4d51 Rev= 7.17
S:  Product=DELL Laser Mouse
C:* #Ifs= 1 Cfg#= 1 Atr=a0 MxPwr=100mA
I:* If#= 0 Alt= 0 #EPs= 1 Cls=03(HID  ) Sub=01 Prot=02 Driver=usbhid
E:  Ad=81(I) Atr=03(Int.) MxPS=   5 Ivl=10ms

Dans le cas présent, on remarque que ce périphérique (une souris) fait partie de la classe HID et utilise le pilote usbhid. Rien d’anormal jusque la, sauf qu’une majorité de périphériques sont de classe HID alors que le pilote générique usbhid est incapable de les prendre en charge correctement. Pire encore, si l’on développe un nouveau pilote, il ne pourra pas être utilisé pour le périphérique tant que que dernier ne sera pas détaché du pilote usbhid. Bien entendu le problème reste le même sur une autre classe que HID mais cette dernière est la plus fréquente.

La prise en compte d’un tel périphérique peut donc être complexe à gérer de manière automatique, surtout si l’on doit prendre en compte l’insertion/suppression du périphérique et pour cela le service UDEV peut être d’un grand secours. Ce dernier a été introduit avec la série de noyau 2.6 afin de gérer efficacement l’ajout et la suppression des périphériques dans un système, soit le chargement des modules .ko et la gestion dynamique des entrées dans le répertoire /dev. Concrètement, UDEV correspond à un démon udevd et un ensemble de fichiers de configuration dans /etc/udev. La configuration du service UDEV peut être ardue et nous nous limiterons aujourd’hui à la prise en charge d’un périphérique USB. Pour cela il suffit d’écrire une nouvelle règle UDEV en ajoutant un fichier .rules au répertoire /etc/udev/rules.d.

Lorsque l’on insère un nouveau périphérique USB HID et que son pilote nommé par exemple panicb est installé dans l’arborescence cible du noyau (soit /lib/modules/<version_du_noyau>/extra), le module est chargé automatiquement mais il n’est pas utilisable car le périphérique est déjà attaché au pilote usbhid.

Le périphérique est identifié par un quadruplet hub-port:configuration.interface (exemple 6-2:1.0) qui dépend entre autres de la prise USB choisie. Dans le système, deux fichiers virtuels à partir de /sys permettent de contrôler l’attachement et le détachement du périphérique à un pilote donné. Le détachement s’effectue par la commande :

# echo -n "6-2:1.0" > /sys/bus/usb/drivers/usbhid/unbind

L’attachement au nouveau pilote par la commande :

# echo -n "6-2:1.0" > /sys/bus/usb/drivers/panicb/bind

Pour automatiser la procédure avec UDEV, il suffit de créer le fichier /etc/udev/rules.d/99-panicb.rules. La valeur 99 indique l’ordre d’exécution. Les champs sont séparés par des virgules.

ATTRS{idVendor}=="1130", 
ATTRS{idProduct}=="0202", 
PROGRAM="/bin/sh -c 'echo -n $id:1.0 > /sys/bus/usb/drivers/usbhid/unbind;  echo -n $id:1.0 > /sys/bus/usb/drivers/panicb/bind'"

Les champs idVendor et idProduct représentent les identifiants matériels du périphérique. La variable $id est fournie par UDEV et contient le hub et le port utilisés, soit dans notre cas 6-2. Une fois la règle ajoutée, on peut mettre à jour la liste avec la commande :

# udevadm control --reload-rules

Dans le cas d’un système embarqué basé sur Busybox, UDEV n’est pas disponible directement car il est remplacé par une version réduite nommée MDEV. Cette dernière est malheureusement très limitée et il faudra adapter UDEV à la cible pour profiter des fonctionnalités exposées.

La documentation complète pour l’écriture des règles est disponible sur http://reactivated.net/writing_udev_rules.html.

2 commentaires sur “ Gérer un pilote USB avec UDEV ”

  1. Gérard Paris
    le 2 mars 2013 à 16 h 25 min

    Bravo pour vos explications claires.
    Appliquées à mon pb de souris Sony RF Receiver, je n’ai pas réussi à la faire fonctionner (Aucun périphérique de ce type, malgré l’utilisation des données de /proc/bus/input/devices), mais j’ai apprécié votre billet.
    Cordialement.
    G. Paris

Répondre à Gérard Paris Annuler la réponse.

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *