L'USB d'un point de vue logiciel
|
|||||
|
Cette page a été réalisée, en avril 2003, par 3 élèves de 2ème année de
SUPÉLEC Campus de Rennes dans le cadre de leur projet :
|
Si vos lectures vous ont amenées jusqu'ici, c'est que vous êtes intéressé par le fait de pouvoir communiquer avec un périphérique USB que vous avez certainement déjà branché sur votre ordinateur. Ce document va vous présenter la manière d'accéder à votre périphérique USB depuis votre OS favori. Sont couverts par ce document les systèmes informatiques Microsoft Windows et Linux.
Commençons par rappeler la manière de dialoguer avec un périphérique simple, tel qu'un port série, un port parallèle, ou une carte
d'extension ISA. Le dialogue se fait en utilisant :
- des ports d'entrées sorties (et éventuellement des entrées sorties projetées en mémoires (MMIO)).
- des interruptions.
La programmation d'un tel périphérique se fait alors de manière simple. On utilise des instructions du style :
>> copier le contenu du registre R1 vers le port d'entrée sortie P0.
ou dans le cas d'entrées sorties projetées en mémoire
>> copier le contenu du registre R1 vers la mémoire P0.
Pour pouvoir programmer un driver de périphérique, il suffit de connaître l'agencement des registres sur le périphérique,
et de dialoguer avec ceux-ci suivant le protocole défini par le constructeur.
Malheureusement pour le connaisseur d'un langage d'assemblage intel 8086, motorola 68000, Microchip PIC ou autre,
ces connaissances ne seront que très peu utiles.
En effet, le périphérique n'est pas connecté physiquement sur le bus de la machine, donc on ne peut dialoguer avec celui-ci qu'avec
de simples instructions d'entrées sorties.
Les caratéristiques de fonctionnement d'un périphérique sont définies par ses terminaisons (EP0, EP1, ...), leur sens (IN
ou OUT), et leur mode de transfert (Control, Interrupt, Bulk ou Isochrone), et le protocole au niveau de chaque terminaison.
On ne peut dialoguer avec un périphérique USB que par l'intermédiaire de l'hôte USB.
Comme si les choses n'étaient pas assez compliquées, la norme USB originelle définit deux types d'hôtes USB, les
hôtes OHCI et UHCI. L'hôte UHCI privilégie la simplicité du matériel au détriement du logiciel, et l'hôte OHCI fait l'inverse.
Les agencements des registres et la manière de communiquer est bien évidemment tout à fait différente pour ces deux hôtes.
Une autre raison qui empêche le dialogue direct avec les périphériques USB est que toutes les requêtes doivent passer par l'hôte
USB. Il faut donc un arbitrage, pour savoir qui peut utiliser l'hôte USB. Ceci ne peut être fait que par le système d'exploitation.
3) Accès USB sous Microsoft Windows 98 et ultérieur
Le support de l'USB à été introduit des Windows 95 OSR2, mais à un niveau assez embrionnaire et peu fiable. On peut
affirmer sans trop mentir que le support USB sous Windows a vraiment commencé à être utilisé à partir de Windows 98.
On dispose de plusieurs moyens d'accéder à un périphérique USB sous Windows :
A compléter par la suite...
Le support USB sous Linux est moins développé que sous Windows. Les drivers de périphériques USB pour Linux ont été développés à partir de 1999, avec les premiers drivers pour claviers et souris USB. La description se base ici sur la version 2.4.
Il existe plusieurs moyens d'accéder à un périphérique USB sous linux.
Seule la dernière méthode sera détaillée ici. Elle utilise l'"USB device filesystem", ou en raccourci "usbdevfs" ou "usbfs". Il s'agit d'un système de fichier, se trouvant en général dans le répertoire /proc/bus/usb. (pour la suite, nous utiliserons l'abbréviation usbfs).
Comment utiliser l'usbfs ?
Pour un programme C/C++ : ajouter la directive
| #include <linux/usbdevice_fs.h> |
Pour un autre langage de programmation : il faudra convertir les structures de données et les définitions de constantes des fichiers /usr/include/linux/usb.h, /usr/include/linux/usb_ch9.h, /usr/include/linux/usbdevice_fs.h.
Comment trouver le périphérique
XXX ?
La question est ici de localiser un périphérique connecté
sur une votre ordinateur. L'usbfs contient un répertoire par bus
USB sur la machine. Sur un PC typique, on trouve deux bus USB. Les répertoires
correspondants sont appelés "001" et "002".
Dans chacun de ces répertoires se trouve un fichier par périphérique
USB connecté au bus, hôte USB compris. Les noms de ces fichiers
ne sont pas significatifs, il s'agit en fait d'un simple chiffre.
Voici comme exemple le système de fichier usbfs sur ma machine :
| [root@localhost
fabien]# tree /proc/bus/usb/ /proc/bus/usb/ |-- 001 | `-- 001 |-- 002 | |-- 001 | `-- 002 |-- devices `-- drivers |
Les deux fichiers 001 correspondent au hub de chacun
des deux bus. Le fichier 002 correspond à ma manette de jeux USB.
Je suis sûr qu'une question vous taraude. Comment faire pour savoir
à quel périphérique USB correspond quel fichier ?
Il existe plusieurs méthodes. Le plus simple est d'ouvrir chaque
fichier, et ensuite d'y lire des octets. Les premiers octets reçus
correspondent à une structure C qui est définie dans <linux/usb_ch9.h>.
struct usb_device_descriptor {
__u8 bLength;
__u8 bDescriptorType;__u16 bcdUSB;
__u8 bDeviceClass;
__u8 bDeviceSubClass;
__u8 bDeviceProtocol;
__u8 bMaxPacketSize0;
__u16 idVendor;
__u16 idProduct;
__u16 bcdDevice;
__u8 iManufacturer;
__u8 iProduct;
__u8 iSerialNumber;
__u8 bNumConfigurations;
} __attribute__ ((packed));
On y trouve les principales caractéristiques d'un périphérique USB.
Comment communiquer avec mon périphérique USB ?
La manière de communiquer dépend du type de tranfert (Bulk,
Isochrone, Interrupt, Control).
Le type de transfert plus simple à gérer est certainement
le transfert Bulk.
Il suffit de remplir la structure de données suivante :
struct usbdevfs_bulktransfer {
unsigned int ep;
unsigned int len;
unsigned int timeout; /* in milliseconds */
void *data;
};
et d'appeler la fonction C suivante : ioctl(fd, USBDEVFS_BULK, pointeur sur la structure usbdevfs_bulktransfer);
Exemple de manipulation de ces notions - lecture de données Bulk
:
| int
fd; if(fd != -1) { struct
usb_bulktransfer bulk; bulk.ep
= USB_DIR_IN | 1; ret = ioctl(fd, USBDEVFS_BULK, &bulk); if (ret
< 0) |
Exemple de manipulation de ces notions - écriture de données
bulk :
| int
fd; if(fd != -1) { struct
usb_bulktransfer bulk; bulk.ep
= 1; ret = ioctl(fd, USBDEVFS_BULK, &bulk); if (ret
< 0) |
Les transfers de type Interrupt IN sont beaucoup plus difficiles à
gérer. La principale difficulté réside dans la fait
qu'ils ont lieu de manière asynchrone. Une fois le transfert initialisé,
le programme reçoit des signaux (au sens Unix du terme) qu'il doit
intercepter.
Je préfère utiliser la pédagogie par l'exemple dans
ce cas.
Voici donc un extrait de code d'un programme qui permet de lire les transferts
de type interruption en provenance d'un périphérique HID.
La fontion hid_interrupt_enable()
active la gestion des interruptions USB pour le periphérique dont
le descripteur de fichier est donné.
La fontion hid_interrupt_disable()
arrête la gestion des interruptions.
La fonction hid_signal()
est utilisée en interne (c'est elle qui est appelée à chaque fois qu'un transfert interruption a été reçu).
| /* global variables */ static char buffer[8]; static void hid_signal(int
signum, siginfo_t * si, void* data) if(purb->status
== -2) { int hid_interrupt_enable(DEVICEHANDLE
dh, unsigned char ep, hid_interrupt_handler h) if(hid_intr_fd
== 0) { /*
zeroing buffer*/ /*sigaction
*/ hid_intr_handler
= h; /*
preparing the urb */ ret
= ioctl(hid_intr_fd, USBDEVFS_SUBMITURB, purb); return ret; int hid_interrupt_disable() if(hid_intr_fd) free(hid_intr_urb); return ret; |
A noter : L'extrait de code précédent ne fonctionne pas sous linux 2.4, mais uniquement à partir de linux 2.5 (un bug dans la série 2.4 des noyaux linux fait planter la machine avec un Kernel Panic).
Les liens utiles
www.linux-usb.org : le site centralisant le développement de l'USB sous linux : accès
aux principales documentations, archives des mailing-lists
La documentation USB du noyau : regarder dans /usr/src/linux/Documentation/usb.