
mini-HOWTO de programmation des ports d'entres / sorties sous Linux

Riku Saikkonen

   <Riku.Saikkonen@hut.fi>

   _Jean-Franois Prvost - _Traduction franaise

   _Guillaume Lelarge - _Relecture de la version franaise

   _Jean-Philippe Gurard - _Relecture de la version franaise
   Version 3.0

   13 dcembre 2000

   Ce document prsente les diffrentes faons de programmer des
   entres / sorties pour les architectures Intel x86 ainsi que de les
   diffrentes mthodes permettant l'utilisation de temporisations trs
   courtes pour les applications Linux tournant en mode utilisateur.
     _________________________________________________________________

   _Table des matires_
   1. Introduction
   2. Utilisation des ports d'entres / sorties en langage C

        2.1. La mthode normale
        2.2. Une mthode alternative : /dev/port

   3. Interruptions (IRQ) et accs DMA
   4. Temporisation de haute prcision

        4.1. Temporisations
        4.2. Mesure du temps

   5. D'autres langages de programmation
   6. Quelques ports utiles

        6.1. Le port parallle
        6.2. Le port de manette de jeu
        6.3. Le port srie

   7. Conseils
   8. Problmes et solutions
   9. Code d'exemple
   10. Remerciements
   11. Adaptation franaise

        11.1. Traduction
        11.2. Relecture

1. Introduction

   Ce document traite de la faon de programmer des entres / sorties
   matrielles sur une architecture Intel x86 ainsi que de l'utilisation
   de temporisations trs courtes pour des applications s'excutant en
   mode utilisateur sous Linux. Ce document est un descendant du trs
   court IO-Port mini-HOWTO du mme auteur.

   Copyright  1995-2000 Riku Saikkonen. Voyez le Linux HOWTO copyright
   pour plus de dtails.

   Si vous avez des remarques ou des corrections, n'hsitez pas 
   m'crire en anglais  l'adresse <Riku.Saikkonen@hut.fi> ...
     _________________________________________________________________

2. Utilisation des ports d'entres / sorties en langage C

2.1. La mthode normale

   Les routines pour accder aux ports d'entres / sorties sont situes
   dans le fichier d'en-tte /usr/include/asm/io.h (ou
   linux/include/asm-i386/io.h dans les sources du noyau Linux). Ces
   routines sont des macros, il suffit donc de dclarer #include
   <asm/io.h>; dans votre code source sans avoir besoin de bibliothques
   additionnelles.

    cause d'une limitation de gcc (prsente dans toutes les versions que
   je connais, egcs y compris) vous _devez_ compiler le code source qui
   fait appel  ces routines avec le drapeau d'optimisation (_gcc -O1_ ou
   plus), ou alternativement en dclarant #define extern static avant la
   ligne #include <asm/io.h> (n'oubliez pas de rajouter ensuite #undef
   extern).

   Pour le dbogage, vous pouvez compiler avec les drapeaux suivants
   (tout du moins avec les versions les plus rcentes de gcc) : _gcc -g
   -O_. Il faut savoir que l'optimisation engendre un comportement
   parfois bizarre de la part du dbogueur. Si cela vous pose un rel
   problme, vous pouvez toujours utiliser les routines d'accs aux ports
   d'entres / sorties dans un fichier source spar, et ne compiler que
   ce fichier avec le drapeau d'optimisation activ.
     _________________________________________________________________

2.1.1. Les permissions

   Avant d'accder aux ports, vous devez donner  votre programme la
   permission de le faire. Pour cela, il vous faut faire appel  la
   fonction ioperm() (dclare dans unistd.h et dfinie dans le noyau)
   quelque part au dbut de votre programme (avant tout accs aux ports
   d'entres / sorties). La syntaxe est la suivante :
   ioperm(premier_port, nombre, activer), o premier_port est le numro
   du premier port auquel on souhaite avoir accs et nombre le nombre de
   ports conscutifs auxquels on veut avoir la permission d'accder. Par
   exemple, ioperm(0x300, 5, 1) donnerait accs aux ports 0x300 jusqu'
   0x304 (au total 5 ports). Le dernier argument est une valeur boolenne
   spcifiant si on autorise l'accs aux ports (vrai [1]) ou si on le
   restreint (faux [0]). Pour activer l'accs  plusieurs ports non
   conscutifs, vous pouvez faire plusieurs appels  ioperm(). Reportez
   vous  la page de manuel ioperm(2) pour plus de dtails sur la
   syntaxe.

   L'appel  ioperm() dans votre programme ncessite les privilges de
   super utilisateur (root). Il faut donc que votre programme soit
   excut en tant qu'utilisateur root, ou qu'il soit rendu setuid root.
   Vous pouvez abandonner les privilges d'utilisateur root aprs l'appel
    ioperm(). Il n'est pas impratif d'abandonner de faon explicite les
   privilges d'accs aux ports en utilisant ioperm( ... , 0 )  la fin
   de votre programme, ceci est fait automatiquement lorsque le processus
   se termine.

   L'utilisation de setuid() par un utilisateur non privilgi ne
   supprime pas l'accs accord aux ports par ioperm(). En revanche, lors
   d'un fork(), le processus fils n'hrite pas des permissions de son
   pre (qui lui les garde).

   La fonction ioperm() permet de contrler l'accs aux ports de 0x000 
   0x3ff uniquement. Pour les ports suprieurs, vous devez utiliser
   iopl() qui ouvre un accs  tous les ports d'un coup. Pour donner 
   votre programme l'accs  _tous_ les ports d'entres / sorties (soyez
   certains de ce que vous faites car l'accs  des ports inappropris
   peut avoir des consquences dsastreuses pour votre systme), il
   suffit de passer  la fonction un argument de valeur 3 (iopl(3)).
   Reportez-vous  la page de manuel iopl(2) pour plus de dtails.
     _________________________________________________________________

2.1.2. L'accs aux ports

   Pour lire un octet (8 bits) sur un port, un appel  inb(port) retourne
   la valeur de l'octet lu. Pour l'criture d'un octet, il suffit
   d'appeler la fonction outb(valeur, port) (attention  l'ordre des
   paramtres). La lecture d'un mot (16 bits) sur les ports x et x+1 (un
   octet sur chaque port pour constituer un mot grce  l'instruction
   assembleur inw), faites appel  inw(x). Enfin, pour l'criture d'un
   mot sur les deux ports, utilisez outw(value, x). Si vous n'tes pas
   certain quant  la fonction  utiliser (octet ou mot), il est sage de
   se cantonner  l'appel de inb() et outb(). La plupart des
   priphriques sont conus pour des accs sur un octet. Notez que
   toutes les instructions d'accs aux ports ncessitent un temps
   d'excution d'au minimum une microseconde.

   Les macros inb_p(), outb_p(), inw_p() et outw_p() fonctionnent de
   manire identique  celles voques prcdemment  l'exception du fait
   qu'elles effectuent un court temps de pause additionnel aprs l'accs
   au port (environ une microseconde). Vous avez la possibilit
   d'allonger ce temps de pause  quatre microsecondes avec la directive
   #define REALLY_SLOW_IO avant de dclarer #include <asm/io.h>. Ces
   macros utilisent normalement une criture sur le port 0x80 pour leur
   temps de pause (sauf en dclarant un #define SLOW_IO_BY_JUMPING, qui
   est en revanche moins prcis). Vous devez donc au pralable autoriser
   l'accs au port 0x80 avec ioperm() (l'criture sur le port 0x80 ne
   devrait avoir aucun effet indsirable sur votre systme).

   Si vous tes  la recherche de mthodes plus souples d'utilisation,
   lisez la suite ...

   Des pages de manuel pour ioperm(2), iopl(2) et les macros dcrites
   ci-dessus sont disponibles dans les collections assez rcentes des
   pages de manuel Linux.
     _________________________________________________________________

2.2. Une mthode alternative : /dev/port

   Un autre moyen d'accder aux ports d'entres / sorties est d'ouvrir en
   lecture ou en criture le priphrique /dev/port (un priphrique en
   mode caractre, numro majeur 1, mineur 4) au moyen de la fonction
   open(). Notons que les fonctions en f*() de la bibliothque stdio font
   appel  des tampons mmoires internes, il vaut donc mieux les viter.
   Il suffit ensuite, comme dans le cas d'un fichier, de se positionner
   sur l'octet appropri au moyen de la fonction lseek() (l'octet 0 du
   fichier quivaut au port 0x00, l'octet 1 au port 0x01, et ctera) et
   d'en lire (read()) ou crire (write()) un octet ou un mot.

   Il est vident que l'application doit avoir la permission d'accder au
   priphrique /dev/port pour que cette mthode fonctionne. Cette faon
   de faire reste certainement plus lente que la premire, mais elle ne
   ncessite ni optimisation lors de la compilation ni appel  ioperm().
   L'accs aux privilges de super-utilisateur n'est pas impratif non
   plus, si vous donnez les permissions adquates  un utilisateur ou un
   groupe pour accder  /dev/port (cela reste tout de mme une trs
   mauvaise ide du point de vue de la scurit du systme, puisqu'il
   devient possible de porter atteinte au systme, peut-tre mme
   d'obtenir le statut de root en utilisant /dev/port pour accder
   directement aux disques durs, cartes rseaux, et ctera).

   Il n'est pas possible d'utiliser les fonctions select(2) ou poll(2)
   pour lire /dev/port puisque l'lectronique du systme n'a pas la
   possibilit d'avertir le microprocesseur qu'une valeur a chang sur un
   port d'entre.
     _________________________________________________________________

3. Interruptions (IRQ) et accs DMA

   Vous ne pouvez tout simplement pas utiliser directement les
   interruptions ou l'accs DMA depuis un processus en mode utilisateur.
   Pour cela, il vous faut dvelopper un pilote pour le noyau.
   Reportez-vous au Linux Kernel Hacker's Guide pour plus de dtails et
   au code source du noyau pour des exemples.

   Vous avez cependant la possibilit de dsactiver les interruptions
   depuis une application en mode utilisateur, mais cela peut s'avrer
   dangereux (mme les pilotes du noyau ne le font que pour des priodes
   de temps trs brves). Aprs appel  iopl(3), vous pouvez dsactiver
   les interruptions en utilisant asm("cli"); et les ractiver avec
   asm("sti");.
     _________________________________________________________________

4. Temporisation de haute prcision

4.1. Temporisations

   Avant toutes choses, il est important de prciser l'impossibilit de
   garantir un contrle prcis des temps d'excution de processus en mode
   utilisateur du fait de la nature multitche du noyau Linux. Votre
   processus peut tre mis en sommeil  n'importe quel moment pour une
   dure allant de 10 millisecondes  quelques secondes (sur un systme
   dont la charge est trs importante). Malgr tout, pour la plupart des
   applications utilisant les ports d'entres / sorties, cela n'est pas
   trs important. Si vous voulez minimiser cet inconvnient, vous pouvez
   donner  votre processus une priorit plus haute (reportez-vous  la
   page de manuel de nice(2)) ou faire appel  l'ordonnancement
   temps-rel (voir ci-aprs).

   Si vous souhaitez obtenir une prcision de temporisation plus leve
   que celle qu'offre les processus en mode utilisateur usuels, sachez
   qu'il existe des possibilits de support  temps-rel  en mode
   utilisateur. Les noyaux Linux de la srie 2.x permettent de travailler
   en quasi temps-rel. Pour les dtails, reportez-vous  la page de
   manuel de sched_setscheduler(2). Il existe galement des versions
   spciales du noyau offrant un vrai ordonnancement temps-rel.
     _________________________________________________________________

4.1.1. Avec sleep() et usleep()

   Commenons tout d'abord par les appels de temporisation les plus
   simples. Pour des temporisation de plusieurs secondes, le meilleur
   choix est probablement la fonction sleep(). Pour des dures au minimum
   de l'ordre de dizaines de millisecondes (10 millisecondes semblent
   tre la dure minimum), usleep() devrait s'avrer suffisant. Ces
   fonctions librent l'accs au microprocesseur pour d'autres processus,
   vitant ainsi le gaspillage du temps machine. Les pages de manuel de
   sleep(3) et usleep(3) vous donneront plus de prcisions.

   Pour des temporisations de moins de 50 millisecondes (en fonction de
   la cadence du microprocesseur, de la machine ainsi que de la charge du
   systme), redonner le processeur aux autres processus prend normment
   de temps. En effet l'ordonnanceur des tches du noyau Linux (en tout
   cas pour les microprocesseurs de la famille x86) prend gnralement au
   moins 10  30 millisecondes avant de rendre le contrle au processus.
   De ce fait, dans les temporisations de courte dure, usleep(3)
   effectue en ralit une pause plus longue que celle spcifie en
   paramtre, prenant au moins 10 millisecondes supplmentaires.
     _________________________________________________________________

4.1.2. nanosleep()

   Dans les noyaux Linux de la srie 2.0.x est apparu l'appel systme
   nanosleep() (voir la page de manuel de nanosleep(2)) permettant
   d'endormir ou de retarder un processus pendant un laps de temps trs
   court (quelques microsecondes ou plus).

   Pour des attentes =< 2 millisecondes, si (et seulement si) votre
   processus fonctionne en ordonnancement quasi temps-rel (au moyen de
   sched_setscheduler()), nanosleep() fait appel  une boucle d'attente ;
   si tel n'est pas le cas, le processus s'endort simplement tout comme
   avec usleep().

   La boucle d'attente utilise udelay() (une fonction interne au noyau
   utilise par beaucoup de pilotes), la dure de celle-ci tant calcule
   en fonction du nombre de BogoMips. La vitesse de ce type de boucle
   d'attente est une des grandeurs que les BogoMips permettent de mesurer
   de faon prcise. Voyez /usr/include/asm/delay.h pour plus de dtails
   quant  son fonctionnement.
     _________________________________________________________________

4.1.3. Temporisations grce aux ports d'entre / sortie

   Les accs aux ports d'entre / sortie sont un autre moyen d'obtenir
   des temporisations. L'criture ou la lecture d'un octet sur le port
   0x80 (voir ci-dessus la procdure  suivre) devrait avoir pour
   consquence un retard d'une microseconde, indpendamment du type et de
   la cadence du microprocesseur. Vous pouvez donc procder de la sorte
   afin d'obtenir un retard de quelques microsecondes. L'criture sur ce
   port ne devrait pas avoir d'effets secondaires sur une machine
   classique, pour preuve certains pilotes du noyau font appel  cette
   mthode. C'est galement de cette manire que {in|out}[bw]_p()
   effectue une pause (voir asm/io.h).

   Plus prcisment, une opration de lecture ou d'criture sur la
   plupart des ports dans l'intervalle 0x000-0x3ff prend 1 microseconde.
   Par exemple, si vous utilisez directement le port parallle, il suffit
   d'utiliser des inb() additionnels sur ce port pour obtenir une
   temporisation.
     _________________________________________________________________

4.1.4. Temporisations en assembleur

   Si vous connaissez le type et la frquence du processeur de la machine
   sur laquelle votre programme va s'excuter, vous pouvez coder en dur
   les temporisations en faisant appel  certaines instructions
   assembleur. Rappelez-vous cependant qu' tout moment votre processus
   peut-tre mis en attente par l'ordonnanceur et, de ce fait, les
   temporisations peuvent s'avrer plus longues que souhaites.

   Pour les donnes du tableau ci-dessous, la frquence interne du
   microprocesseur dtermine le nombre de cycles d'horloge consomms. Par
   exemple, pour un microprocesseur  50 Mhz (un 486DX-50), un cycle
   d'horloge dure 1/50000000 de seconde (soit 200 nanosecondes).

   Instruction  cycles d'horloge i386 cycles d'horloge i486
   xchg %bx,%bx 3                     3
   nop          3                     1
   or %ax,%ax   2                     1
   mov %ax,%ax  2                     1
   add %ax,0    2                     1

   Les cycles d'horloges du Pentium devraient tre les mmes que ceux du
   486,  l'exception du Pentium Pro / II, dont l'instruction add %ax, 0
   peut ne consommer qu'un demi cycle d'horloge. Cette instruction
   peut-tre parfois tre combine avec une autre (cependant, du fait de
   l'algorithme d'excution hors de squence (_out-of-order_), il n'est
   pas ncessaire qu'il s'agisse d'une instruction conscutive dans le
   flot).

   Les instructions nop et xchg du tableau ne devraient pas avoir
   d'effets secondaires. Les autres, en revanche, peuvent modifier le
   registre d'tat, mais cela reste sans gravit puisque gcc devrait le
   dtecter. xchg %bx,%bx reste un bon compromis comme instruction de
   temporisation.

   Pour utiliser ces instructions, placez un appel asm("instruction")
   dans votre programme. La syntaxe d'appel de ces instructions est telle
   qu'numre dans le tableau ci-dessus. Si vous prfrez grouper
   plusieurs instructions dans le mme appel  asm, il vous suffit de les
   sparer par des points-virgules. Par exemple asm("nop ; nop ; nop ;
   nop") excute quatre fois l'instruction nop, effectuant une
   temporisation de quatre cycles d'horloge sur un i486 ou un Pentium (ou
   douze cycles sur un i386).

   Les instructions asm() sont directement intgres au code par gcc,
   vitant ainsi la perte de temps que pourrait engendrer un appel de
   fonction classique.

   Les temporisations de moins d'un cycle d'horloge sont impossibles sur
   les architectures x86 d'Intel.
     _________________________________________________________________

4.1.5. rdtsc pour Pentium

   Avec les microprocesseurs Pentium, vous avez la possibilit de
   connatre le nombre de cycles d'horloge couls depuis le dernier
   redmarrage avec le code C suivant (qui fait appel  l'instruction
   appele RDTSC) :
            extern __inline__ unsigned long long int rdtsc()
            {
            unsigned long long int x;
            __asm__ volatile (".byte 0x0f, 0x31" : "=A" (x));
            return x;
            }

   Vous pouvez scruter cette valeur dans une boucle d'attente afin
   d'obtenir un retard correspondant au nombre de cycles d'horloge que
   vous souhaitez.
     _________________________________________________________________

4.2. Mesure du temps

   Pour des dures de la prcision d'une seconde, il est certainement
   plus simple d'utiliser la fonction time(). Pour obtenir plus de
   prcision, gettimeofday() a une prcision d'environ une microseconde
   (mais rappelez-vous de l'ordonnancement dj voqu prcdemment).
   Pour les Pentium, le fragment de code rdtsc ci-dessus est prcis au
   cycle d'horloge prs.

   Si vous souhaitez que votre processus reoive un signal aprs un
   certain laps de temps, utilisez setitimer() ou alarm(). Voyez les
   pages de manuel de ces fonctions pour plus de dtails.
     _________________________________________________________________

5. D'autres langages de programmation

   Les descriptions ci-dessus se concentrent principalement sur le
   langage C. Cependant, ces exemples devraient tre directement
   applicables au C++ et  l'objective C. En assembleur, il vous suffit
   de faire appel  ioperm() ou iopl(), comme en C, et d'utiliser ensuite
   directement les ports d'entre / sortie.

   Avec d'autres langages,  moins de pouvoir insrer du code assembleur,
   du code C ou utiliser les appels systmes, il est probablement plus
   simple d'crire un programme C trs simple offrant des fonctions
   prenant en charge les temporisations et les accs aux ports
   d'entres / sorties souhaits, de le compiler et de le lier avec le
   reste de votre application. Ou vous pouvez utiliser /dev/port comme
   dcrit plus haut.
     _________________________________________________________________

6. Quelques ports utiles

   Voici quelques informations utiles pour la programmation des ports les
   plus utiliss. Ces ports peuvent tre directement interfacs avec une
   lectronique logique de type TTL (ou CMOS).

   Si vous avez l'intention d'utiliser ces ports ou des ports classiques
   en vue d'applications pour lesquels ils ont t conus (par exemple
   pour contrler une imprimante ou un modem ordinaire), vous auriez
   intrt  utiliser les pilotes dj existants (qui sont gnralement
   inclus dans le noyau) plutt que de programmer directement les ports
   comme dcrit dans ce document. Cette section s'adresse principalement
   aux personnes dsireuses de connecter aux ports d'entre / sortie
   standards de leur PC des crans  cristaux liquides, des moteurs
   pas--pas ou d'autres montages lectroniques faits maison.

   En revanche, si votre but est de piloter un priphrique grand public,
   tel qu'un scanner, disponible sur le march depuis quelques temps
   dj, essayez plutt de savoir si un pilote a dj t dvelopp. Le
   Hardware-HOWTO est un bon point de dpart pour mener cette
   investigation.

   http://www.hut.fi/Misc/Electronics/ est galement une excellente
   source d'information sur la connexion de priphriques  un ordinateur
   et sur l'lectronique en gnrale.
     _________________________________________________________________

6.1. Le port parallle

   L'adresse de base du port parallle (appele BASE ci-dessous) est
   0x3bc pour /dev/lp0, 0x378 pour /dev/lp1 et 0x278 pour /dev/lp2. Si
   vous souhaitez piloter un matriel fonctionnant comme une imprimante
   classique, voyez le Printing-HOWTO.

   En plus du mode standard d'criture-seule dcrit ci-dessous, il existe
   un mode  tendu  bidirectionnel sur la plupart des ports parallles.
   Pour des informations  ce sujet ainsi que sur les rcents modes ECP /
   EPP (et le standard IEEE 1284 en gnral), voici deux adresses :
   http://www.fapo.com/ et http://www.beyondlogic.org/. Rappelez-vous
   cependant que puisque vous ne pouvez pas utiliser les requtes
   d'interruptions (IRQ) ou l'accs DMA dans un programme en mode
   utilisateur, vous serez certainement oblig d'crire un pilote pour le
   noyau afin d'exploiter les modes ECP / EPP. Il me semble que quelqu'un
   est dj en train de dvelopper un tel pilote, mais je n'en sais pas
   plus  ce sujet.

   Le port BASE+0 (port de donnes) contrle les signaux de donnes du
   port (respectivement D0  D7 pour les bits 0  7; tats : 0 = bas
   (0V), 1 = haut (5V)). Une criture sur ce port positionne l'tat des
   broches correspondantes. La lecture retourne la dernire valeur crite
   en mode standard ou tendu, sinon les donnes prsentes sur les
   broches connectes  un autre priphrique en mode de lecture tendue.

   Le port BASE+1 (port d'tat) est en lecture seule et retourne l'tat
   des signaux d'entre suivants :

     * Bits 0 et 1 sont rservs.
     * Bit 2 tat d'IRQ (ne correspond pas  une broche, je ne sais pas
       comment il fonctionne)
     * Bit 3 ERROR (1 = tat haut)
     * Bit 4 SLCT (1 = tat haut)
     * Bit 5 PE (1 = tat haut)
     * Bit 6 ACK (1 = tat haut)
     * Bit 7 -BUSY (0 = tat haut)

   Le port BASE+2 (port de contrle) est en criture seule (une lecture
   sur celui-ci retourne la dernire valeur crite) et contrle les
   signaux d'tat suivants :

     * Bit 0 -STROBE (0 = tat haut)
     * Bit 1 -AUTO_FD_XT (0 = tat haut)
     * Bit 2 INIT (1 = tat haut)
     * Bit 3 -SLCT_IN (0 = tat haut)
     * Bit 4 active la requte d'interruption (IRQ) du port parallle
       (qui survient lors d'une transition bas-vers-haut de la broche
       ACK) lorsqu'il est positionn  1.
     * Bit 5 contrle la direction du mode tendu (0 = criture, 1 =
       lecture) et est en criture seule (une lecture sur ce bit ne
       retournera pas de valeur significative).
     * Bits 6 et 7 sont rservs.

   Brochage (d'un connecteur femelle sub-D 25 sur le port) (e = entre,
   s = sortie) :
 1(e/s) -STROBE,   2(e/s) D0,          3(e/s) D1,          4(e/s) D2,
 5(e/s) D3,        6(e/s) D4,          7(e/s) D5,          8(e/s) D6,
 9(e/s) D7,       10(e) ACK,          11(e) -BUSY,        12(e) PE,
13(e) SLCT,       14(s) -AUTO_FD_XT,  15(e) ERROR,        16(s) INIT,
17(s) -SLCT_IN,   18-25 masse

   Les spcifications donnes par IBM indiquent que les broches 1, 14, 16
   et 17 (les sorties de contrle) sont de type collecteur ouvert ramen
    5,0 V au moyen d'une rsistance de 4,7 kohm (intensit de fuite de
   20 mA, de source 0,55 mA, tat haut de sortie 5,0 V moins tension de
   _pull-up_). Le reste des broches ont une intensit de fuite de 24 mA,
   de source de 15 mA et leur voltage  l'tat haut est au minimum de
   2,4 V. L'tat bas pour toutes les broches est au maximum de 0,5 V. Les
   ports parallles d'autres types que celui d'IBM peuvent s'carter de
   ce standard. Pour plus d'informations  ce sujet, voyez l'adresse :
   http://www.hut.fi/Misc/Electronics/circuits/lptpower.html.

   Avertissement

   Faites trs attention aux masses ! J'ai dj grill  plusieurs
   reprises des ports parallles en les connectant alors que l'ordinateur
   tait sous tension. Utiliser un port parallle non intgr  la carte
   mre peut s'avrer une bonne solution pour viter de trop grands
   dsagrments. Vous pouvez obtenir un second port parallle pour votre
   machine au moyen d'une carte multi-entres / sorties  petit prix. Il
   vous suffit de dsactiver les ports dont vous n'avez pas besoin, puis
   de configurer le port parallle de la carte sur une adresse libre.
   Vous n'avez pas besoin de vous proccuper de l'IRQ du port parallle
   si vous n'y faites pas appel.
     _________________________________________________________________

6.2. Le port de manette de jeu

   Le port de manette de jeu est accessible aux adresses 0x200-0x207. Si
   vous souhaitez contrler une manette de jeu ordinaire, vous serez
   probablement mieux servi en utilisant les pilotes distribus avec le
   noyau.

   Brochage (pour un connecteur sub-D 15 femelle) :

     * 1,8,9,15 : +5 V (Alimentation)
     * 4,5,12 : masse
     * 2,7,10,14 : entres numriques, respectivement BA1, BA2, BB1 et
       BB2
     * 3,6,11,13 : entres  analogiques , respectivement AX, AY, BX et
       BY

   Les broches fournissant une tension de +5 V semblent souvent tre
   connectes directement sur l'alimentation de la carte mre, ce qui
   peut leur permettre d'obtenir pas mal de puissance, en fonction de la
   carte mre, du bloc d'alimentation et du port de manette de jeu.

   Les entres numriques sont utilises pour les boutons des deux
   manettes de jeu (manette A et manette B, avec deux boutons chacune)
   que vous pouvez connecter au port. Ces entres devraient tre de
   niveau TTL classique, ainsi vous pouvez lire directement leurs valeurs
   sur le port d'tat (voir plus bas). Une vritable manette de jeu
   retourne un tat bas (0 V) lorsque le bouton est press et un tat
   haut dans l'autre cas (une tension de 5 V des broches de
   d'alimentation au travers d'une rsistance de 1 kohm).

   Les pseudo entres analogiques mesurent en ralit la rsistance. Le
   port de manette de jeu comporte un quadruple monostable (une puce de
   type NE558) connect aux quatre entres. Pour chaque entre, il y a
   une rsistance de 2,2 kohm entre la broche d'entre et la sortie du
   monostable, et un condensateur de 0,01 F entre la sortie du
   monostable et la masse. Une vritable de manette de jeu a un
   potentiomtre pour chaque axe (X et Y), connect entre le +5 V et la
   broche d'entre approprie (AX ou AY pour la manette A, ou BX ou BY
   pour la manette B).

   Lorsqu'il est activ, le monostable initialise ses lignes de sortie 
   un tat haut (5 V) et attend que chaque condensateur de temporisation
   atteigne une tension de 3,3 V avant de mettre la sortie correspondante
    un tat bas. La dure de l'tat haut de la sortie du temporisateur
   est proportionnelle  la rsistance du potentiomtre de la manette de
   jeu (en clair, la position du manche de la manette de jeu de l'axe
   appropri) comme expliqu ci-dessous :

     R = (t - 24.2) / 0.011

   o R est la rsistance en ohm, du potentiomtre et t la dure de
   l'tat haut de la sortie, en microsecondes.

   Pour effectuer une lecture sur les entres analogiques, vous devez
   tout d'abord activer le monostable (au moyen d'une criture sur le
   port, voir plus bas), puis scruter l'tat des quatre axes au moyen de
   lectures rptes jusqu' la transition  un tat bas, permettant
   ainsi de mesurer la dure de l'tat haut. Cette scrutation est trs
   gourmande en temps machine. De plus, sur un systme multitche non
   temps-rel tel que Linux (en mode utilisateur normal) le rsultat
   n'est pas trs prcis, du fait de l'impossibilit de scruter le port
   constamment ( moins d'utiliser un pilote noyau et de dsactiver la
   gestion des interruptions pendant la scrutation, mais sachez que vous
   consommerez encore plus de temps machine). Si vous savez  l'avance
   que le signal va mettre un certain temps (plusieurs dizaines de
   millisecondes) avant de basculer, vous pouvez faire appel  usleep()
   avant de procder  la scrutation afin de laisser un peu de temps
   machine aux autres processus.

   Le seul port d'entres / sorties auquel vous avez besoin d'accder est
   le port 0x201 (les autres ports se comportent de faon identique ou ne
   ragissent pas). Toute criture sur ce port, peu importe la valeur
   envoye, active le temporisateur. Une lecture retourne l'tat des
   signaux d'entre :

     * Bit 0 : AX (tat de la sortie du temporisateur, 1 = tat haut)
     * Bit 1 : AY (tat de la sortie du temporisateur, 1 = tat haut)
     * Bit 2 : BX (tat de la sortie du temporisateur, 1 = tat haut)
     * Bit 3 : BY (tat de la sortie du temporisateur, 1 = tat haut)
     * Bit 4 : BA1 (entre numrique, 1 = tat haut)
     * Bit 5 : BA2 (entre numrique, 1 = tat haut)
     * Bit 6 : BB1 (entre numrique, 1 = tat haut)
     * Bit 7 : BB2 (entre numrique, 1 = tat haut)
     _________________________________________________________________

6.3. Le port srie

   Si le priphrique avec lequel vous communiquez est  peu prs
   compatible avec le standard RS-232, vous devriez tre en mesure
   d'utiliser pour cela le port srie. Le pilote srie du noyau Linux
   devrait suffire pour la plupart des applications (vous ne devriez pas
   avoir  programmer le port directement, de plus vous devriez
   probablement crire un pilote pour le noyau si vous souhaitiez le
   faire), il est en effet trs polyvalent et l'usage de dbits
   non-standards ne devrait pas poser de problmes particuliers.

   Voyez la page de manuel de termios(3), le code source du pilote
   (linux/drivers/char/serial.c) ainsi que la page
   http://www.easysw.com/~mike/serial/ pour plus d'informations sur la
   programmation des ports sries des systmes Unix.
     _________________________________________________________________

7. Conseils

   Si vous souhaitez obtenir une bonne acquisition analogique, vous
   pouvez connecter un CAN ou un CNA au port parallle (conseil : pour
   l'alimentation, utilisez soit le port de manette de jeu, soit un
   connecteur d'alimentation de disque que vous relierez  l'extrieur de
   votre botier,  moins que vous n'utilisiez un priphrique peu
   consommateur d'nergie, et que vous puissiez utiliser le port
   parallle lui-mme pour l'alimenter, sinon utilisez tout simplement
   une source d'alimentation externe). Vous pouvez galement investir
   dans l'achat d'une carte d'acquisition numrique / analogique ou
   analogique / numrique (la plupart des cartes d'acquisition les plus
   anciennes et les plus lentes s'appuient sur l'usage de ports de
   communication externes). Sinon, si vous pouvez vous satisfaire de
   seulement un ou deux canaux d'acquisition, de rsultats peu prcis et
   sans doute d'un mauvais niveau du zro, l'utilisation d'une carte son
   bas de gamme supporte par le noyau Linux devrait rpondre  votre
   attente (et vous permettre de faire des acquisitions rapides).

   Avec des priphriques analogiques de prcision, une mauvaise mise 
   la masse peut gnrer de mauvaises mesures lors de lectures ou
   d'critures analogiques. Si vous rencontrez ce type de dsagrment,
   vous pouvez isoler lectriquement le priphrique de votre ordinateur
   au moyen d'optocoupleurs (sur _toutes_ les lignes entre le
   priphrique et l'ordinateur). Vous pouvez essayer de tirer
   l'alimentation lectrique des optocoupleurs de votre ordinateur (les
   signaux de broches non utilises du port peuvent peut-tre vous
   fournir une puissance suffisante) afin d'obtenir une meilleure
   isolation.

   Si vous tes  la recherche d'un logiciel de conception de circuits
   imprims sous Linux, sachez qu'il existe une application libre pour
   X11 appele Pcb qui devrait vous rendre de grands services, tout du
   moins si vous n'avez pas de projets trop compliqus. Ce logiciel est
   inclus dans la plupart des distributions Linux et est disponible 
   l'adresse suivante : ftp://sunsite.unc.edu/pub/Linux/apps/circuits/
   (nom de l'archive pcb-*).
     _________________________________________________________________

8. Problmes et solutions

   Q1.
          J'obtiens une erreur de segmentation lorsque j'essaye d'accder
          aux ports.

   R1.
          Soit votre programme n'a pas les privilges de super
          utilisateur, soit l'appel  ioperm() n'a pas russi pour une
          raison ou une autre. Vrifiez la valeur de retour de ioperm().
          Vrifiez galement que vous accdez bien aux ports que vous
          avez activs pralablement avec ioperm() (voir Q3). Si vous
          faites appel aux macros de temporisation (inb_p(), outb_p(), et
          ctera), pensez aussi  utiliser ioperm() pour obtenir l'accs
          au port 0x80.

   Q2.
          Je n'arrive  trouver nulle part la dfinition de in*(),
          out*(), de plus gcc se plaint de rfrences non-dfinies.

   R2.
          Vous n'avez pas lanc la compilation avec les drapeaux
          d'optimisation (-O), en consquence gcc n'a pas pu trouver les
          macros dfinies dans asm/io.h. Ou alors, vous n'avez pas inclus
          du tout la ligne #include <asm/io.h> dans votre code.

   Q3.
          out*() ne fait rien ou ne retourne que des valeurs bizarres.

   R3.
          Vrifiez l'ordre des paramtres, ceux-ci devraient tre comme
          ce qui suit : outb(valeur, port) et non pas outportb(port,
          valeur) comme en MS-DOS.

   Q4.
          Je souhaite contrler un priphrique standard, tel qu'un
          priphrique RS-232, une imprimante parallle, une manette de
          jeu, ...

   R4.
          Vous auriez plutt intrt  utiliser les pilotes dj
          existants (dans le noyau Linux ou un serveur X ou ailleurs)
          pour ce faire. Ces pilotes sont gnralement assez polyvalents.
          Ainsi ils arrivent mme en gnral  faire fonctionner les
          priphriques sortant lgrement des standards. Voyez la note
          d'information ci-dessus sur les ports standards pour de la
          documentation  leur sujet.
     _________________________________________________________________

9. Code d'exemple

   Voici un exemple de programme simple permettant l'accs aux ports
   d'entres / sorties :
/*
* exemple.c : un exemple trs simple d'accs aux ports d'E/S
*
* Ce programme ne fait rien d'utile, juste une criture sur le port,
* une pause, puis une lecture sur le mme port.
*  compiler avec  gcc -O2 -o exemple exemple.c et 
* excuter en tant que root avec  ./exemple .
*/

#include <stdio.h>
#include <unistd.h>
#include <asm/io.h>

#define BASEPORT 0x378 /* lp1 */

int main()
{
/* Obtention de l'accs aux ports */
if (ioperm(BASEPORT, 3, 1)) {perror("ioperm"); exit(1);}

/* Initialisation de tous les signaux de donnes (D0-D7)  l'tat bas (0) */
outb(0, BASEPORT);

/* Dormons pendant un moment (100 ms) */
usleep(100000);

/* Lecture sur le port d'tat (BASE+1) et affichage du rsultat */
printf("status : %d\n", inb(BASEPORT + 1));

/* Nous n'avons plus besoin de l'accs aux ports */
if (ioperm(BASEPORT, 3, 0)) {perror("ioperm"); exit(1);}

exit(0);
}

/* fin d'exemple.c */
     _________________________________________________________________

10. Remerciements

   Beaucoup trop de personnes ont contribu  l'criture de ce document
   pour que je puisse en faire la liste ici, mais merci  tous. Je n'ai
   pas pu rpondre  toutes les contributions reues, je m'en excuse,
   mais encore merci pour votre aide.
     _________________________________________________________________

11. Adaptation franaise

11.1. Traduction

   La traduction franaise de ce document a t ralise par
   Jean-Franois Prvost <prevost@2cse-group.com>.
     _________________________________________________________________

11.2. Relecture

   La relecture de ce document a t ralise par Guillaume Lelarge
   <gleu@wanadoo.fr> et Jean-Philippe Gurard
   <jean-philippe.guerard@laposte.net>.
