1. Utilisation des ports d'E/S dans les programmes C
Contenu de cette section
1.1 Méthode classique
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.
1.2 Problèmes
Je récolte des segmentation faults lorsque j'accède auxports~!
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().
Je ne trouve pas les définitions des fonctions in*(), out*(),gcc se plaint de références inconnues~!
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>.
1.3 Une autre méthode
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.
1.4 Interruptions (IRQs) et DMA
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
|