Les routines permettant l'accès aux ports d'E/S sont définies dans /usr/include/asm/io.h (ou linux/include/asm-i386/io.h dans les sources du noyau). Ce sont des macros "inline", il suffit donc de #inclure <asm/io.h>~; Aucune autre bibliothèque (library, NDT) n'est requise.
Du fait d'une limitation de gcc (au moins jusqu'à la version 2.7.0 comprise), vous devez compiler tout code source utilisant ces routines avec les options d'optimisation (i.e. gcc -O). Une autre limitation de gcc empêche de compiler à la fois avec les options d'optimisation et de mise au point (-g). Cela signifie que si vous désirez utiliser gdb sur un programme manipulant les ports d'E/S, il est judicieux de mettre les routines utilisant les ports d'E/S dans un fichier source séparé, puis, lors de la mise au point, de compiler ce fichier source avec l'option d'optimisation, le reste avec l'option de mise au point.
Avant d'utiliser un port, il faut donner à votre programme la permission de le faire. Il suffit pour cela d'appeler la fonction ioperm(2) (déclarée dans unistd.h et définie dans le noyau) quelque part au début de votre application (avant tout accès à un port d'E/S). La syntaxe est ioperm(from,num,turn_on), où from représente le premier numéro de port et num le nombre de ports consécutifs à rendre accessibles. Par exemple, ioperm(0x300,5,1); autoriserait l'accès aux ports 0x300 à 0x304 (5 ports au total). Le dernier argument est un booléen précisant si l'on désire donner (vrai (1)) ou retirer (faux (0)) l'accès au port. Pour autoriser plusieurs ports non consécutifs, on peut appeler ioperm() autant que nécessaire. Consultez la page de manuel de ioperm(2) pour avoir des précisions sur la syntaxe.
Votre programme ne peut appeler ioperm() que s'il possède les privilèges de root~; pour cela, vous devez soit le lancer comme utilisateur root, soit le rendre suid root. Il devrait être possible (Je n'ai pas essayé~; SVP, envoyez-moi un message si vous l'avez fait) d'abandonner les privilèges de root une fois l'accès aux ports obtenu par ioperm(). Il n'est pas nécessaire d'appeler ioperm(...,0) à la fin du programme pour abandonner explicitement les droits, cette procédure étant automatique.
Les privilèges accordés par ioperm() demeurent lors d'un fork(), exec() ou setuid() en un utilisateur autre que root.
ioperm() ne permet l'accès qu'aux ports 0x000 à 0x3ff~; pour les ports supérieurs, il faut utiliser iopl(2) (qui donne des droits sur tous les ports d'un coup)~; je ne l'ai jamais fait, regardez le manuel pour en savoir plus. Je suppose que l'argument level doit valoir 3 pour autoriser l'accès. SVP, envoyez-moi un message si vous avez des précisions à ce sujet.
Maintenant, l'utilisation proprement dite... Pour lire un octet sur un port, appelez inb(port); qui retourne l'octet correspondant. Pour écrire un octet, appelez outb(value, port); (attention à l'ordre des paramètres). Pour lire un mot sur les ports x et x+1 (mot formé par un octet de chaque port, comme l'instruction INW en assembleur), appelez inw(x);. Pour écrire un mot vers deux ports, outw(value,x);.
Les macros inb_p(), outb_p(), inw_p() et outw_p() fonctionnent de la même façon que celles précédemment évoquées, mais elles respectent, en plus, une courte attente (environ une microseconde) après l'accès au port; vous pouvez passer l'attente à quatre microsecondes en #définissant REALLY_SLOW_IO avant d'inclure asm/io.h. Ces macros créent cette temporisation en écrivant (à moins que vous ne #définissiez SLOW_IO_BY_JUMPING, moins précis certainement) dans le port 0x80, vous devez donc préalablement autoriser l'accès à ce port 0x80 avec ioperm() (les écriture vers le port 0x80 ne devraient pas affecter le fonctionnement du système par ailleurs). Pour des méthodes de temporisations plus souples, lisez plus loin.
Les pages de manuels associées à ces macros paraitront dans une version future des pages de manuels de Linux.
Soit votre programme n'a pas les privilèges de root, soit l'appel à ioperm() a échoué pour quelqu'autre raison. Vérifiez la valeur de retour de ioperm().
Vous n'avez pas compilé avec l'option d'optimisation (-O), et donc gcc n'a pas pu définir les macros dans asm/io.h. Ou alors vous n'avez pas #inclus <asm/io.h>.
Une autre méthode consiste à ouvrir /dev/port (un périphérique caractère, major number 1, minor number 4) en lecture et/ou écriture (en utilisant les fonctions habituelles d'accès aux fichiers, open() etc. - les fonctions f*() de stdio utilisent des tampons internes, évitez-les). Puis positionnez-vous (seek, NDT) au niveau de l'octet approprié dans le fichier (position 0 dans le fichier = port 0, position 1 = port 1, etc.), lisez-y ou écrivez-y ensuite un octet ou un mot. Je n'ai pas vraiment essayé et je ne suis pas absolument certain que cela marche ainsi~; envoyez-moi un message si vous avez des détails.
Bien évidemment, votre programme doit posséder les bons droits d'accès en lecture/écriture sur /dev/port. Cette méthode est probablement plus lente que la méthode traditionnelle évoquée auparavant.
Pour autant que je sache, il n'est pas possible d'utiliser les IRQs ou DMA directement dans un programme en mode utilisateur. Vous devez écrire un pilote dans le noyau~ voyez le Linux Kernel Hacker's Guide (khg-x.yy) pour les détails et les sources du noyau pour des exemples.
Chapitre suivant
Table des matières de ce chapitre, Table des matières générale
Début du document, Début de ce chapitre